From ed515c059db8b996a0e6ba66ee5e4584ac56832d Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Thu, 6 Jul 2023 01:13:38 -0700 Subject: [PATCH] Stub sqlite engine --- src/sql/engines/sqlite.zig | 398 +++---------------------------------- src/sql/lib.zig | 26 ++- 2 files changed, 47 insertions(+), 377 deletions(-) diff --git a/src/sql/engines/sqlite.zig b/src/sql/engines/sqlite.zig index 99d1f50..a01bdb2 100644 --- a/src/sql/engines/sqlite.zig +++ b/src/sql/engines/sqlite.zig @@ -1,384 +1,44 @@ +const common = @import("../lib.zig"); const std = @import("std"); -const util = @import("util"); -const common = @import("./common.zig"); const c = @cImport({ - @cInclude("sqlite3.h"); + //@cInclude("sqlite3.h"); }); -const Uuid = util.Uuid; -const DateTime = util.DateTime; +const QueryOptions = common.QueryOptions; +const ExecError = common.ExecError; +const SqlValue = common.SqlValue; +const Results = common.Results; + const Allocator = std.mem.Allocator; -fn getCharPos(text: []const u8, offset: c_int) struct { row: usize, col: usize } { - var row: usize = 0; - var col: usize = 0; - var i: usize = 0; +pub const Engine = struct { + //db: *c.sqlite3, + dummy: usize = 0, - if (offset > text.len) return .{ .row = 0, .col = 0 }; - - while (i != offset) : (i += 1) { - if (text[i] == '\n') { - row += 1; - col = 0; - } 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 {}; + pub fn db(self: *Engine) common.Db { + return .{ + .ptr = self, + .vtable = &.{ + .close = Engine.close, + .exec = Engine.exec, }, }; - - 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 bindArgument(self: Db, stmt: *c.sqlite3_stmt, idx: u15, val: anytype) !void { - if (comptime std.meta.trait.isZigString(@TypeOf(val))) { - return self.bindString(stmt, idx, val); - } - - const T = @TypeOf(val); - - switch (@typeInfo(T)) { - .Union => { - const arr = if (@hasDecl(T, "toCharArray")) - 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 close(ctx: *anyopaque) void { + const self: *Engine = @ptrCast(*Engine, @alignCast(@alignOf(Engine), ctx)); + _ = self; } - 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); - }, - } + fn exec( + ctx: *anyopaque, + sql: []const u8, + args: []const SqlValue, + opts: QueryOptions, + alloc: Allocator, + ) ExecError!Results { + const self: *Engine = @ptrCast(*Engine, @alignCast(@alignOf(Engine), ctx)); + _ = .{ self, sql, args, opts, alloc }; + @panic("unimplemented"); } }; - -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; - } -} diff --git a/src/sql/lib.zig b/src/sql/lib.zig index 3964125..6a3bccf 100644 --- a/src/sql/lib.zig +++ b/src/sql/lib.zig @@ -27,8 +27,6 @@ pub const ConstraintError = error{ ConstraintViolation, }; -pub const OpenError = error{BadConnection} || UnexpectedError; - pub const ExecError = error{ Cancelled, BadConnection, @@ -49,7 +47,7 @@ pub const ExecError = error{ } || ConstraintError || UnexpectedError; pub const Db = struct { - const VTable = struct { + pub const VTable = struct { /// Closes the database connection 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, }; - vtable: VTable, + vtable: *const VTable, + ptr: *anyopaque, }; pub const ColumnCountError = error{OutOfRange}; @@ -75,14 +74,15 @@ pub const RowError = error{ } || ConstraintError || UnexpectedError; pub const Results = struct { - const VTable = struct { + pub const VTable = struct { columnCount: *const fn (ctx: *anyopaque) ColumnCountError!ColumnIndex, columnIndex: *const fn (ctx: *anyopaque) ColumnIndexError!ColumnIndex, row: *const fn (ctx: *anyopaque) RowError!?Row, finish: *const fn (ctx: *anyopaque) void, }; - vtable: VTable, + vtable: *const VTable, + ptr: *anyopaque, }; pub const GetError = error{ @@ -91,7 +91,7 @@ pub const GetError = error{ } || UnexpectedError; pub const Row = struct { - const VTable = struct { + pub const VTable = struct { isNull: *const fn (ctx: *anyopaque, idx: ColumnIndex) GetError!bool, getStr: *const fn (ctx: *anyopaque, idx: ColumnIndex) GetError![]const u8, 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, }; - 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; +}