const std = @import("std"); const Allocator = std.mem.Allocator; const backend = @import("backend.zig"); const log = @import("../logging.zig"); pub const BlockDocumentRow = struct { id: []const u8, created: []const u8, updated: []const u8, name: ?[]const u8, data: []const u8, is_anonymous: bool, block_type_id: []const u8, block_type_name: ?[]const u8, block_schema_id: []const u8, }; pub fn insert( id: []const u8, name: ?[]const u8, data: []const u8, is_anonymous: bool, block_type_id: []const u8, block_type_name: ?[]const u8, block_schema_id: []const u8, ) !void { backend.db.exec( \\INSERT INTO block_document (id, name, data, is_anonymous, block_type_id, block_type_name, block_schema_id) \\VALUES (?, ?, ?, ?, ?, ?, ?) , .{ id, name, data, @as(i32, if (is_anonymous) 1 else 0), block_type_id, block_type_name, block_schema_id, }) catch |err| { log.err("database", "insert block_document error: {}", .{err}); return err; }; } pub fn getById(alloc: Allocator, id: []const u8) !?BlockDocumentRow { var rows = backend.db.query( \\SELECT id, created, updated, name, data, is_anonymous, \\ block_type_id, block_type_name, block_schema_id \\FROM block_document WHERE id = ? , .{id}) catch return null; defer rows.deinit(); if (rows.next()) |row| { return rowToBlockDocument(alloc, row); } return null; } pub fn getByTypeSlugAndName( alloc: Allocator, block_type_slug: []const u8, name: []const u8, ) !?BlockDocumentRow { var rows = backend.db.query( \\SELECT bd.id, bd.created, bd.updated, bd.name, bd.data, bd.is_anonymous, \\ bd.block_type_id, bd.block_type_name, bd.block_schema_id \\FROM block_document bd \\JOIN block_type bt ON bd.block_type_id = bt.id \\WHERE bt.slug = ? AND bd.name = ? , .{ block_type_slug, name }) catch return null; defer rows.deinit(); if (rows.next()) |row| { return rowToBlockDocument(alloc, row); } return null; } /// SQL for update - dialect-specific for datetime function const update_sql = struct { const sqlite_with_schema = "UPDATE block_document SET data = ?, block_schema_id = ?, updated = datetime('now') WHERE id = ?"; const sqlite_no_schema = "UPDATE block_document SET data = ?, updated = datetime('now') WHERE id = ?"; const postgres_with_schema = "UPDATE block_document SET data = ?, block_schema_id = ?, updated = NOW()::TEXT WHERE id = ?"; const postgres_no_schema = "UPDATE block_document SET data = ?, updated = NOW()::TEXT WHERE id = ?"; }; pub fn update( id: []const u8, data: []const u8, block_schema_id: ?[]const u8, ) !void { if (block_schema_id) |schema_id| { const sql = switch (backend.db.dialect) { .sqlite => update_sql.sqlite_with_schema, .postgres => update_sql.postgres_with_schema, }; backend.db.exec(sql, .{ data, schema_id, id }) catch |err| { log.err("database", "update block_document error: {}", .{err}); return err; }; } else { const sql = switch (backend.db.dialect) { .sqlite => update_sql.sqlite_no_schema, .postgres => update_sql.postgres_no_schema, }; backend.db.exec(sql, .{ data, id }) catch |err| { log.err("database", "update block_document error: {}", .{err}); return err; }; } } pub fn list(alloc: Allocator, limit: usize) ![]BlockDocumentRow { var results = std.ArrayListUnmanaged(BlockDocumentRow){}; errdefer results.deinit(alloc); var rows = backend.db.query( \\SELECT id, created, updated, name, data, is_anonymous, \\ block_type_id, block_type_name, block_schema_id \\FROM block_document ORDER BY created DESC LIMIT ? , .{@as(i64, @intCast(limit))}) catch |err| { log.err("database", "list block_documents error: {}", .{err}); return err; }; defer rows.deinit(); while (rows.next()) |row| { try results.append(alloc, rowToBlockDocument(alloc, row)); } return results.toOwnedSlice(alloc); } pub fn delete(id: []const u8) !bool { const affected = backend.db.execWithRowCount( "DELETE FROM block_document WHERE id = ?", .{id}, ) catch |err| { log.err("database", "delete block_document error: {}", .{err}); return err; }; return affected > 0; } pub fn listByTypeSlug(alloc: Allocator, block_type_slug: []const u8, limit: usize) ![]BlockDocumentRow { var results = std.ArrayListUnmanaged(BlockDocumentRow){}; errdefer results.deinit(alloc); var rows = backend.db.query( \\SELECT bd.id, bd.created, bd.updated, bd.name, bd.data, bd.is_anonymous, \\ bd.block_type_id, bd.block_type_name, bd.block_schema_id \\FROM block_document bd \\JOIN block_type bt ON bd.block_type_id = bt.id \\WHERE bt.slug = ? \\ORDER BY bd.created DESC LIMIT ? , .{ block_type_slug, @as(i64, @intCast(limit)) }) catch |err| { log.err("database", "list block_documents by type error: {}", .{err}); return err; }; defer rows.deinit(); while (rows.next()) |row| { try results.append(alloc, rowToBlockDocument(alloc, row)); } return results.toOwnedSlice(alloc); } fn rowToBlockDocument(alloc: Allocator, row: anytype) BlockDocumentRow { return .{ .id = alloc.dupe(u8, row.text(0)) catch "", .created = alloc.dupe(u8, row.text(1)) catch "", .updated = alloc.dupe(u8, row.text(2)) catch "", .name = if (row.text(3).len > 0) alloc.dupe(u8, row.text(3)) catch null else null, .data = alloc.dupe(u8, row.text(4)) catch "{}", .is_anonymous = row.int(5) != 0, .block_type_id = alloc.dupe(u8, row.text(6)) catch "", .block_type_name = if (row.text(7).len > 0) alloc.dupe(u8, row.text(7)) catch null else null, .block_schema_id = alloc.dupe(u8, row.text(8)) catch "", }; }