SpacetimeDB-Zig/src/spacetime/types.zig
2025-03-28 16:40:20 -04:00

601 lines
19 KiB
Zig

const std = @import("std");
const utils = @import("utils.zig");
const spacetime = @import("../spacetime.zig");
const console_log = spacetime.console_log;
const TableId = spacetime.TableId;
const SpacetimeValue = spacetime.SpacetimeValue;
const SpacetimeError = spacetime.SpacetimeError;
pub const Str = []const u8;
pub const SumTypeVariant = struct {
name: ?Str,
algebraic_type: AlgebraicType,
};
pub const SumType = struct {
variants: []const SumTypeVariant,
};
pub const ArrayType = struct {
elem_ty: []const AlgebraicType,
};
pub const AlgebraicType = union(enum) {
Ref: AlgebraicTypeRef,
Sum: SumType,
Product: ProductType,
Array: ArrayType,
String: void,
Bool: void,
I8: void,
U8: void,
I16: void,
U16: void,
I32: void,
U32: void,
I64: void,
U64: void,
I128: void,
U128: void,
I256: void,
U256: void,
F32: void,
F64: void,
};
pub const Typespace = struct {
types: []const AlgebraicType,
};
pub const RawIdentifier = Str;
pub const AlgebraicTypeRef = struct {
inner: u32,
};
pub const RawIndexAlgorithm = union(enum) {
BTree: []const u16,
Hash: []const u16,
Direct: u16,
};
pub const RawIndexDefV9 = struct {
name: ?Str,
accessor_name: ?Str,
algorithm: RawIndexAlgorithm,
};
pub const RawUniqueConstraintDataV9 = union(enum) {
Columns: []const u16,
};
pub const RawConstraintDataV9 = union(enum) {
unique: RawUniqueConstraintDataV9,
};
pub const RawConstraintDefV9 = struct {
name: ?Str,
data: RawConstraintDataV9
};
pub const RawSequenceDefV9 = struct {
name: ?Str,
column: u16,
start: ?i128,
min_value: ?i128,
max_value: ?i128,
increment: i128
};
pub const RawScheduleDefV9 = struct {
name: ?Str,
reducer_name: Str,
scheduled_at_column: u16
};
pub const TableType = enum {
System,
User,
};
pub const TableAccess = enum {
Public,
Private,
};
pub const RawTableDefV9 = struct {
name: RawIdentifier,
product_type_ref: AlgebraicTypeRef,
primary_key: []const u16,
indexes: []const RawIndexDefV9,
constraints: []const RawConstraintDefV9,
sequences: []const RawSequenceDefV9,
schedule: ?RawScheduleDefV9,
table_type: TableType,
table_access: TableAccess,
};
pub const ProductTypeElement = struct {
name: Str,
algebraic_type: AlgebraicType,
};
pub const ProductType = struct {
elements: []const ProductTypeElement,
};
pub const Lifecycle = enum {
Init,
OnConnect,
OnDisconnect,
None,
};
fn getUnionSize(data: anytype) usize {
return 1 + switch(data) {
inline else => |field| getDataSize(field),
};
}
fn getStructSize(data: anytype) usize {
var size: usize = 0;
inline for(std.meta.fields(@TypeOf(data))) |field| {
size += getDataSize(@field(data, field.name));
}
return size;
}
fn getDataSize(data: anytype) usize {
return switch(@TypeOf(data)) {
[]const u8 => 4 + data.len,
i8, u8, i16, u16, i32, u32,
i64, u64, i128, u128, i256, u256,
f32, f64 => |data_type| @sizeOf(data_type),
else => |data_type| switch(@typeInfo(data_type)) {
.@"struct" => getStructSize(data),
.@"union" => getUnionSize(data),
.@"enum" => @sizeOf(data_type),
.@"enum_literal" => @compileError("enum literals can't be supported without the type info"),
.@"optional" => 1 + getDataSize(data),
else => {
@compileLog(data_type);
@compileError("Unsupported type in getStructSize");
},
},
};
}
fn getUnionData(data: anytype, mem: *[]u8) void {
const tag: u8 = @intFromEnum(data);
appendValue(tag, mem);
switch(data) {
inline else => |field| {
appendValue(field, mem);
},
}
}
fn getStructData(data: anytype, mem: *[]u8) void {
const fields = std.meta.fields(@TypeOf(data));
inline for(fields) |field| {
appendValue(@field(data, field.name), mem);
}
}
fn appendValue(data: anytype, mem: *[]u8) void {
const data_type = @TypeOf(data);
switch(data_type) {
[]const u8 => {
std.mem.bytesAsValue(u32, mem.*[0..4]).* = data.len;
std.mem.copyForwards(u8, mem.*[4..], data);
mem.* = mem.*[4 + data.len ..];
},
i8, u8, i16, u16, i32, u32,
i64, u64, i128, u128, i256, u256,
f32, f64 => {
std.mem.bytesAsValue(data_type, mem.*[0..@sizeOf(data_type)]).* = data;
mem.* = mem.*[@sizeOf(data_type)..];
},
else => blk: {
if(@typeInfo(data_type) == .@"struct") {
getStructData(data, mem);
break :blk;
} else if(@typeInfo(data_type) == .@"union") {
getUnionData(data, mem);
break :blk;
} else if(@typeInfo(data_type) == .@"optional") {
mem.*[0] = @intFromBool(data == null);
mem.* = mem.*[1..];
if(data != null) {
appendValue(data.?, mem);
}
break :blk;
} else if(@typeInfo(data_type) == .@"enum") {
std.mem.bytesAsValue(data_type, mem.*[0..@sizeOf(data_type)]).* = data;
mem.* = mem.*[@sizeOf(data_type)..];
break :blk;
}
@compileLog(data_type);
@compileError("failed to append type!");
}
}
}
pub fn StructSerializer(struct_type: type) fn(std.mem.Allocator, struct_type) std.mem.Allocator.Error![]u8 {
return struct {
pub fn serialize(allocator: std.mem.Allocator, data: struct_type) ![]u8 {
const size: usize = switch(@typeInfo(@TypeOf(data))) {
.@"struct" => getStructSize(data),
else => @compileError("A table schema has to be a struct!"),
};
const mem = try allocator.alloc(u8, size);
var offset_mem = mem;
_ = getStructData(data, &offset_mem);
return mem;
}
}.serialize;
}
pub fn UnionDeserializer(union_type: type) fn(allocator: std.mem.Allocator, *[]const u8) std.mem.Allocator.Error!*union_type {
return struct {
pub fn deserialize(allocator: std.mem.Allocator, data: *[]const u8) std.mem.Allocator.Error!*union_type {
const ret = try allocator.create(union_type);
var offset_mem = data.*;
const tagType = u8;
const tag: std.meta.Tag(union_type) = @enumFromInt(std.mem.bytesAsValue(tagType, offset_mem[0..@sizeOf(tagType)]).*);
offset_mem = offset_mem[@sizeOf(tagType)..];
switch(tag) {
inline else => |union_field| {
const field = std.meta.fields(union_type)[@intFromEnum(union_field)];
switch(field.type) {
[]const u8 => {
const len = std.mem.bytesAsValue(u32, offset_mem[0..4]).*;
const str = try allocator.dupe(u8, offset_mem[4..(4+len)]);
@field(ret.*, field.name) = str;
offset_mem = offset_mem[4+len ..];
},
i8, u8, i16, u16, i32, u32,
i64, u64, i128, u128, i256, u256,
f32, f64 => {
@field(ret.*, field.name) = std.mem.bytesAsValue(field.type, offset_mem[0..@sizeOf(field.type)]).*;
offset_mem = offset_mem[@sizeOf(field.type)..];
},
else => blk: {
if(@typeInfo(field.type) == .@"struct") {
@field(ret.*, field.name) = (try StructDeserializer(field.type)(allocator, &offset_mem)).*;
break :blk;
} else if(@typeInfo(field.type) == .@"union") {
@field(ret.*, field.name) = (try UnionDeserializer(field.type)(allocator, &offset_mem)).*;
break :blk;
}
@compileLog(field.type);
@compileError("Unsupported type in StructDeserializer");
},
}
}
}
data.* = offset_mem;
return ret;
}
}.deserialize;
}
pub fn StructDeserializer(struct_type: type) fn(allocator: std.mem.Allocator, *[]const u8) std.mem.Allocator.Error!*struct_type {
return struct {
pub fn deserialize(allocator: std.mem.Allocator, data: *[]const u8) std.mem.Allocator.Error!*struct_type {
const ret = try allocator.create(struct_type);
var offset_mem = data.*;
const fields = std.meta.fields(struct_type);
inline for(fields) |field| {
switch(field.type) {
[]const u8 => {
const len = std.mem.bytesAsValue(u32, offset_mem[0..4]).*;
const str = try allocator.dupe(u8, offset_mem[4..(4+len)]);
@field(ret.*, field.name) = str;
offset_mem = offset_mem[4+len ..];
},
i8, u8, i16, u16, i32, u32,
i64, u64, i128, u128, i256, u256,
f32, f64 => {
std.log.debug("field_type: {} (offset_mem.len: {})", .{field.type, offset_mem.len});
@field(ret.*, field.name) = std.mem.bytesAsValue(field.type, offset_mem[0..@sizeOf(field.type)]).*;
offset_mem = offset_mem[@sizeOf(field.type)..];
},
else => blk: {
if(@typeInfo(field.type) == .@"struct") {
@field(ret.*, field.name) = (try StructDeserializer(field.type)(allocator, &offset_mem)).*;
break :blk;
} else if(@typeInfo(field.type) == .@"union") {
@field(ret.*, field.name) = (try UnionDeserializer(field.type)(allocator, &offset_mem)).*;
break :blk;
}
@compileLog(field.type);
@compileError("Unsupported type in StructDeserializer");
},
}
}
data.* = offset_mem;
std.log.debug("StructDeserializer Ended!", .{});
return ret;
}
}.deserialize;
}
pub const BoundVariant = enum(u8)
{
Inclusive = 0,
Exclusive = 1,
Unbounded = 2,
};
noinline fn lineInfo() usize {
return @returnAddress();
}
pub fn Iter(struct_type: type) type {
return struct {
allocator: std.mem.Allocator,
handle: spacetime.RowIter,
buffer: [0x20_000]u8 = undefined,
contents: ?[]u8 = null,
last_ret: SpacetimeValue = .OK,
pub fn next(self: *@This()) spacetime.ReducerError!?*struct_type {
std.log.debug("line: {} (handle: {})", .{358, self.handle});
var buffer_len: usize = undefined;
//while(true)
//{
var ret: spacetime.SpacetimeValue = self.last_ret;
if(self.contents == null or self.contents.?.len == 0) {
std.log.debug("line: {} (contents: {any})", .{364, self.contents});
if(self.handle._inner == spacetime.RowIter.INVALID._inner) {
std.log.debug("line: {}", .{366});
self.contents = null;
return null;
}
std.log.debug("line: {}", .{371});
buffer_len = self.buffer.len;
std.log.debug("line: {}", .{374});
ret = try spacetime.retMap(spacetime.row_iter_bsatn_advance(self.handle, @constCast(@ptrCast(&self.buffer)), &buffer_len));
std.log.debug("ret: {}", .{ret});
std.log.debug("self.buffer[0..buffer_len]: {any} {any} {any}", .{(&self.buffer).ptr, self.buffer.len, buffer_len});
self.contents = self.buffer[0..buffer_len];
std.log.debug("line: {}", .{379});
if(ret == .EXHAUSTED) {
std.log.debug("line: {}", .{382});
self.handle = spacetime.RowIter.INVALID;
}
std.log.debug("line: {}", .{385});
}
std.log.debug("{}", .{struct_type});
return StructDeserializer(struct_type)(self.allocator, &(self.contents.?));
//}
}
pub fn one_or_null(self: *@This()) ?*struct_type {
defer self.close();
return self.next() catch null;
}
pub fn close(self: *@This()) void {
_ = spacetime.row_iter_bsatn_close(self.handle);
self.handle = spacetime.RowIter.INVALID;
self.contents = null;
}
};
}
pub fn Column2ORM(comptime table_name: []const u8, comptime column_name: [:0]const u8) type {
const table = blk: {
for(spacetime.tables) |table| {
if(std.mem.eql(u8, table_name, table.name.?)) {
break :blk table;
}
}
@compileError("Table " ++ table_name ++ " does not exist!");
};
const struct_type = table.schema;
const column_type = utils.getMemberDefaultType(struct_type, column_name);
const wrapped_type = @Type(.{
.@"struct" = std.builtin.Type.Struct{
.backing_integer = null,
.decls = &.{},
.fields = &.{
std.builtin.Type.StructField{
.alignment = @alignOf(column_type),
.default_value = null,
.is_comptime = false,
.name = column_name,
.type = column_type,
}
},
.is_tuple = false,
.layout = .auto,
}
});
return struct {
allocator: std.mem.Allocator,
pub fn filter(self: @This(), val: wrapped_type) !Iter(struct_type) {
const temp_name: []const u8 = table_name ++ "_" ++ column_name ++ "_idx_btree";
var id = spacetime.IndexId{ ._inner = std.math.maxInt(u32)};
const err = try spacetime.retMap(spacetime.index_id_from_name(temp_name.ptr, temp_name.len, &id));
std.log.debug("index_id_from_name({}): {x}", .{err, id._inner});
const nVal: struct{ bounds: BoundVariant, val: wrapped_type } = .{
.bounds = .Inclusive,
.val = val,
};
const size: usize = getStructSize(nVal);
const mem = try self.allocator.alloc(u8, size);
var offset_mem = mem;
defer self.allocator.free(mem);
getStructData(nVal, &offset_mem);
const data = mem[0..size];
const rstart: []u8 = data[0..];
const rend: []u8 = data[0..];
var rowIter: spacetime.RowIter = undefined;
_ = try spacetime.retMap(spacetime.datastore_index_scan_range_bsatn(
id,
data.ptr, 0,
spacetime.ColId{ ._inner = 0},
rstart.ptr, rstart.len,
rend.ptr, rend.len,
&rowIter
));
return Iter(struct_type){
.allocator = self.allocator,
.handle = rowIter,
};
}
pub fn find(self: @This(), val: wrapped_type) !?*struct_type {
var iter = try self.filter(val);
return iter.one_or_null();
}
pub fn delete(self: @This(), val: wrapped_type) !void {
const temp_name: []const u8 = table_name ++ "_" ++ column_name ++ "_idx_btree";
var id = spacetime.IndexId{ ._inner = std.math.maxInt(u32)};
_ = spacetime.index_id_from_name(temp_name.ptr, temp_name.len, &id);
const nVal: struct{ bounds: BoundVariant, val: wrapped_type } = .{
.bounds = .Inclusive,
.val = val,
};
const size: usize = getStructSize(nVal);
const mem = try self.allocator.alloc(u8, size);
var offset_mem = mem;
defer self.allocator.free(mem);
getStructData(nVal, &offset_mem);
const data = mem[0..size];
const rstart: []u8 = data[0..];
const rend: []u8 = data[0..];
var deleted_fields: u32 = undefined;
_ = spacetime.datastore_delete_by_index_scan_range_bsatn(
id,
data.ptr, 0,
spacetime.ColId{ ._inner = 0},
rstart.ptr, rstart.len,
rend.ptr, rend.len,
&deleted_fields
);
}
};
}
pub fn Table2ORM(comptime table_name: []const u8) type {
const table = blk: {
for(spacetime.tables) |table| {
if(std.mem.eql(u8, table_name, table.name.?)) {
break :blk table;
}
}
};
const struct_type = table.schema;
return struct {
allocator: std.mem.Allocator,
pub fn insert(self: @This(), data: struct_type) !void {
var id: TableId = undefined;
_ = spacetime.table_id_from_name(table_name.ptr, table_name.len, &id);
const raw_data = try StructSerializer(struct_type)(self.allocator, data);
defer self.allocator.free(raw_data);
var raw_data_len: usize = raw_data.len;
_ = spacetime.datastore_insert_bsatn(id, raw_data.ptr, &raw_data_len);
}
pub fn iter(self: @This()) Iter(struct_type) {
var id: TableId = undefined;
_ = spacetime.table_id_from_name(table_name.ptr, table_name.len, &id);
var rowIter: spacetime.RowIter = undefined;
_ = spacetime.datastore_table_scan_bsatn(id, &rowIter);
return Iter(struct_type){
.allocator = self.allocator,
.handle = rowIter,
};
}
pub fn col(self: @This(), comptime column_name: [:0]const u8) Column2ORM(table_name, column_name) {
return .{
.allocator = self.allocator,
};
}
};
}
pub const Local = struct {
allocator: std.mem.Allocator,
pub fn get(self: @This(), comptime table: []const u8) Table2ORM(table) {
return .{
.allocator = self.allocator,
};
}
};
pub const ReducerContext = struct {
sender: spacetime.Identity,
timestamp: spacetime.Timestamp,
connection_id: spacetime.ConnectionId,
db: Local,
};
pub const ReducerFn = fn(*ReducerContext) void;
pub const RawReducerDefV9 = struct {
name: RawIdentifier,
params: ProductType,
lifecycle: Lifecycle,
};
pub const RawScopedTypeNameV9 = struct {
scope: []RawIdentifier,
name: RawIdentifier,
};
pub const RawTypeDefV9 = struct {
name: RawScopedTypeNameV9,
ty: AlgebraicTypeRef,
custom_ordering: bool,
};
pub const RawMiscModuleExportV9 = enum {
RESERVED,
};
pub const RawSql = []u8;
pub const RawRowLevelSecurityDefV9 = struct {
sql: RawSql,
};
pub const RawModuleDefV9 = struct {
typespace: Typespace,
tables: []const RawTableDefV9,
reducers: []const RawReducerDefV9,
types: []const RawTypeDefV9,
misc_exports: []const RawMiscModuleExportV9,
row_level_security: []const RawRowLevelSecurityDefV9,
};