feat: add atEnd and readField to dump
This commit is contained in:
1 parent
7c74a3e0d3
commit
a3ac6b0c5d
1 file changed
+161
+161
@@ -159,3 +159,164 @@ const CsvReader = struct {
|
|||||||
data: []const u8,
|
data: []const u8,
|
||||||
pos: usize = 0,
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
Reference in new issue
Block a user