-
Notifications
You must be signed in to change notification settings - Fork 101
/
Module.cs
340 lines (300 loc) · 8.91 KB
/
Module.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
namespace SpacetimeDB.Module;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using SpacetimeDB.SATS;
[SpacetimeDB.Type]
public partial struct IndexDef
{
string IndexName;
bool IsUnique;
Runtime.IndexType Type;
uint[] ColumnIds;
public IndexDef(
string name,
Runtime.IndexType type,
bool isUnique,
RawBindings.ColId[] columnIds
)
{
IndexName = name;
IsUnique = isUnique;
Type = type;
ColumnIds = columnIds.Select(id => (uint)id).ToArray();
}
}
[SpacetimeDB.Type]
public partial struct ColumnDef
{
internal string ColName;
AlgebraicType ColType;
public ColumnDef(string name, AlgebraicType type)
{
ColName = name;
ColType = type;
}
}
[SpacetimeDB.Type]
public partial struct ConstraintDef
{
string ConstraintName;
// bitflags should be serialized as bytes rather than sum types
byte Kind;
uint[] ColumnIds;
public ConstraintDef(string name, ColumnAttrs kind, uint[] columnIds)
{
ConstraintName = name;
Kind = (byte)kind;
ColumnIds = columnIds;
}
}
[SpacetimeDB.Type]
public partial struct SequenceDef
{
string SequenceName;
uint ColPos;
Int128 increment;
Int128? start;
Int128? min_value;
Int128? max_value;
Int128 allocated;
public SequenceDef(
string sequenceName,
uint colPos,
Int128? increment = null,
Int128? start = null,
Int128? min_value = null,
Int128? max_value = null,
Int128? allocated = null
)
{
SequenceName = sequenceName;
ColPos = colPos;
this.increment = increment ?? 1;
this.start = start;
this.min_value = min_value;
this.max_value = max_value;
this.allocated = allocated ?? 4_096;
}
}
// Not part of the database schema, just used by the codegen to group column definitions with their attributes.
public struct ColumnDefWithAttrs
{
public ColumnDef ColumnDef;
public ColumnAttrs Attrs;
public ColumnDefWithAttrs(ColumnDef columnDef, ColumnAttrs attrs)
{
ColumnDef = columnDef;
Attrs = attrs;
}
}
[SpacetimeDB.Type]
public partial struct TableDef
{
string TableName;
ColumnDef[] Columns;
IndexDef[] Indices = Array.Empty<IndexDef>();
ConstraintDef[] Constraints;
SequenceDef[] Sequences = Array.Empty<SequenceDef>();
// "system" | "user"
string TableType;
// "public" | "private"
string TableAccess;
public TableDef(string tableName, ColumnDefWithAttrs[] columns)
{
TableName = tableName;
Columns = columns.Select(col => col.ColumnDef).ToArray();
Constraints = columns
// Important: the position must be stored here, before filtering.
.Select((col, pos) => (col, pos))
.Where(pair => pair.col.Attrs != ColumnAttrs.UnSet)
.Select(
pair =>
new ConstraintDef(
$"ct_{tableName}_{pair.col.ColumnDef.ColName}_{pair.col.Attrs}",
pair.col.Attrs,
new[] { (uint)pair.pos }
)
)
.ToArray();
TableType = "user";
TableAccess = tableName.StartsWith('_') ? "private" : "public";
}
}
[SpacetimeDB.Type]
public partial struct TableDesc
{
TableDef Schema;
AlgebraicTypeRef Data;
public TableDesc(TableDef schema, AlgebraicTypeRef data)
{
Schema = schema;
Data = data;
}
}
[SpacetimeDB.Type]
public partial struct ReducerDef
{
string Name;
ProductTypeElement[] Args;
public ReducerDef(string name, params ProductTypeElement[] args)
{
Name = name;
Args = args;
}
}
[SpacetimeDB.Type]
partial struct TypeAlias
{
internal string Name;
internal AlgebraicTypeRef Type;
}
[SpacetimeDB.Type]
partial struct MiscModuleExport : SpacetimeDB.TaggedEnum<(TypeAlias TypeAlias, Unit _Reserved)> { }
[SpacetimeDB.Type]
public partial struct ModuleDef
{
List<AlgebraicType> Types = new();
List<TableDesc> Tables = new();
List<ReducerDef> Reducers = new();
List<MiscModuleExport> MiscExports = new();
public ModuleDef() { }
public AlgebraicTypeRef AllocTypeRef()
{
var index = Types.Count;
var typeRef = new AlgebraicTypeRef(index);
// uninhabited type, to be replaced by a real type
Types.Add(new SumType());
return typeRef;
}
// Note: this intends to generate a valid identifier, but it's not guaranteed to be unique as it's not proper mangling.
// Fix it up to a different mangling scheme if it causes problems.
private static string GetFriendlyName(Type type) =>
type.IsGenericType
? $"{type.Name.Remove(type.Name.IndexOf('`'))}_{string.Join("_", type.GetGenericArguments().Select(GetFriendlyName))}"
: type.Name;
public void SetTypeRef<T>(AlgebraicTypeRef typeRef, AlgebraicType type, bool anonymous = false)
{
Types[typeRef.TypeRef] = type;
if (!anonymous)
{
MiscExports.Add(
new MiscModuleExport
{
TypeAlias = new TypeAlias { Name = GetFriendlyName(typeof(T)), Type = typeRef }
}
);
}
}
public void Add(TableDesc table)
{
Tables.Add(table);
}
public void Add(ReducerDef reducer)
{
Reducers.Add(reducer);
}
}
[System.Flags]
public enum ColumnAttrs : byte
{
UnSet = 0b0000,
Indexed = 0b0001,
AutoInc = 0b0010,
Unique = Indexed | 0b0100,
Identity = Unique | AutoInc,
PrimaryKey = Unique | 0b1000,
PrimaryKeyAuto = PrimaryKey | AutoInc,
PrimaryKeyIdentity = PrimaryKey | Identity,
}
public static class ReducerKind
{
public const string Init = "__init__";
public const string Update = "__update__";
public const string Connect = "__identity_connected__";
public const string Disconnect = "__identity_disconnected__";
}
public interface IReducer
{
SpacetimeDB.Module.ReducerDef MakeReducerDef();
void Invoke(System.IO.BinaryReader reader, Runtime.DbEventArgs args);
}
public static class FFI
{
private static List<IReducer> reducers = new();
private static ModuleDef module = new();
public static void RegisterReducer(IReducer reducer)
{
reducers.Add(reducer);
module.Add(reducer.MakeReducerDef());
}
public static void RegisterTable(TableDesc table) => module.Add(table);
public static AlgebraicTypeRef AllocTypeRef() => module.AllocTypeRef();
public static void SetTypeRef<T>(
AlgebraicTypeRef typeRef,
AlgebraicType type,
bool anonymous = false
) => module.SetTypeRef<T>(typeRef, type, anonymous);
public static RawBindings.Buffer __describe_module__()
{
// replace `module` with a temporary internal module that will register ModuleDef, AlgebraicType and other internal types
// during the ModuleDef.GetSatsTypeInfo() instead of exposing them via user's module.
var userModule = module;
try
{
module = new();
var moduleBytes = ModuleDef.GetSatsTypeInfo().ToBytes(userModule);
var res = RawBindings._buffer_alloc(moduleBytes, (uint)moduleBytes.Length);
return res;
}
catch (Exception e)
{
Runtime.Log($"Error while describing the module: {e}", Runtime.LogLevel.Error);
return RawBindings.Buffer.INVALID;
}
finally
{
module = userModule;
}
}
private static byte[] Consume(this RawBindings.Buffer buffer)
{
var len = RawBindings._buffer_len(buffer);
var result = new byte[len];
RawBindings._buffer_consume(buffer, result, len);
return result;
}
public static RawBindings.Buffer __call_reducer__(
uint id,
RawBindings.Buffer caller_identity,
RawBindings.Buffer caller_address,
ulong timestamp,
RawBindings.Buffer args
)
{
try
{
using var stream = new MemoryStream(args.Consume());
using var reader = new BinaryReader(stream);
reducers[(int)id].Invoke(
reader,
new(caller_identity.Consume(), caller_address.Consume(), timestamp)
);
if (stream.Position != stream.Length)
{
throw new Exception("Unrecognised extra bytes in the reducer arguments");
}
return /* no exception */
RawBindings.Buffer.INVALID;
}
catch (Exception e)
{
var error_str = e.ToString();
var error_bytes = System.Text.Encoding.UTF8.GetBytes(error_str);
return RawBindings._buffer_alloc(error_bytes, (uint)error_bytes.Length);
}
}
}