From a3ac6b0c5d45364e65556041d6821d64d4d6466a Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 2 Sep 2026 21:00:42 +0200 Subject: [PATCH] feat: add atEnd and readField to dump --- src/dump.zig | 161 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/src/dump.zig b/src/dump.zig index 9873ef9..6629d04 100644 --- a/src/dump.zig +++ b/src/dump.zig @@ -159,3 +159,164 @@ const CsvReader = struct { data: []const u8, pos: usize = 0, + fn atEnd(self: *const CsvReader) bool { + return self.pos >= self.data.len; + } + + fn readField(self: *CsvReader, allocator: Allocator) ![]u8 { + var out = std.ArrayList(u8).init(allocator); + errdefer out.deinit(); + if (!self.atEnd() and self.data[self.pos] == '"') { + self.pos += 1; + while (true) { + if (self.atEnd()) return error.MalformedCsv; + const c = self.data[self.pos]; + if (c == '"') { + self.pos += 1; + if (!self.atEnd() and self.data[self.pos] == '"') { + try out.append('"'); + self.pos += 1; + } else { + break; + } + } else { + try out.append(c); + self.pos += 1; + } + } + } else { + while (!self.atEnd()) { + const c = self.data[self.pos]; + if (c == ',' or c == '\n' or c == '\r') break; + try out.append(c); + self.pos += 1; + } + } + return out.toOwnedSlice(); + } + + /// Returns the next record (list of owned field slices), or null at + /// end of input. Caller owns every field and the returned slice. + fn nextRecord(self: *CsvReader, allocator: Allocator) !?[][]u8 { + if (self.atEnd()) return null; + var fields = std.ArrayList([]u8).init(allocator); + errdefer { + for (fields.items) |f| allocator.free(f); + fields.deinit(); + } + + while (true) { + try fields.append(try self.readField(allocator)); + if (self.atEnd()) break; + const c = self.data[self.pos]; + if (c == ',') { + self.pos += 1; + continue; + } else if (c == '\n') { + self.pos += 1; + break; + } else if (c == '\r') { + self.pos += 1; + if (!self.atEnd() and self.data[self.pos] == '\n') self.pos += 1; + break; + } + return error.MalformedCsv; + } + return try fields.toOwnedSlice(); + } +}; + +pub fn importCsvContent(allocator: Allocator, table: *Table, content: []const u8) !usize { + var csv = CsvReader{ .data = content }; + const header = try csv.nextRecord(allocator) orelse return 0; + defer { + for (header) |h| allocator.free(h); + allocator.free(header); + } + + var pkIndex: ?usize = null; + for (header, 0..) |h, i| { + if (std.mem.eql(u8, h, pkField)) { + pkIndex = i; + break; + } + } + const pkIdx = pkIndex orelse return error.MissingPrimaryKey; + + var count: usize = 0; + while (try csv.nextRecord(allocator)) |record| { + defer { + for (record) |f| allocator.free(f); + allocator.free(record); + } + if (record.len != header.len) return error.MalformedCsv; + + var pk: ?Value = null; + defer if (pk) |p| p.free(allocator); + var fields = std.ArrayList(Field).init(allocator); + defer { + for (fields.items) |f| f.value.free(allocator); + fields.deinit(); + } + + for (record, 0..) |cell, i| { + if (cell.len == 0) continue; + const value = try decodeCell(allocator, cell); + if (i == pkIdx) { + pk = value; + } else { + try fields.append(.{ .name = header[i], .value = value }); + } + } + const pkValue = pk orelse return error.MissingPrimaryKey; + try table.put(pkValue, fields.items); + count += 1; + } + return count; +} + +pub fn importCsv(allocator: Allocator, table: *Table, path: []const u8) !usize { + const content = try std.fs.cwd().readFileAlloc(allocator, path, 1 << 30); + defer allocator.free(content); + return importCsvContent(allocator, table, content); +} + +// --------------------------------------------------------------- JSON ----- + +pub fn exportJsonWriter(allocator: Allocator, table: *Table, writer: anytype) !usize { + const rows = try table.scanAll(); + defer querymod.freeRows(allocator, rows); + + const columns = try collectColumns(allocator, rows); + defer allocator.free(columns); + + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + const arenaAllocator = arena.allocator(); + + var array = std.json.Array.init(arenaAllocator); + for (rows) |r| { + var object = std.json.ObjectMap.init(arenaAllocator); + for (columns) |c| { + if (r.get(c)) |v| { + const cell = try encodeCell(arenaAllocator, v); + try object.put(c, .{ .string = cell }); + } + } + try array.append(.{ .object = object }); + } + + try std.json.stringify(std.json.Value{ .array = array }, .{ .whitespace = .indent_2 }, writer); + try writer.writeByte('\n'); + return rows.len; +} + +pub fn exportJson(allocator: Allocator, table: *Table, path: []const u8) !usize { + const file = try std.fs.cwd().createFile(path, .{ .truncate = true }); + defer file.close(); + var buffered = std.io.bufferedWriter(file.writer()); + const n = try exportJsonWriter(allocator, table, buffered.writer()); + try buffered.flush(); + return n; +} +