-
Notifications
You must be signed in to change notification settings - Fork 1k
Expand file tree
/
Copy pathlib.cpp
More file actions
705 lines (587 loc) · 25.4 KB
/
lib.cpp
File metadata and controls
705 lines (587 loc) · 25.4 KB
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
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
// Set log level to TRACE for this module for repeating_test reducer
#define STDB_LOG_LEVEL ::SpacetimeDB::LogLevelValue::TRACE
#include <spacetimedb.h>
#include <variant>
#include <optional>
using namespace SpacetimeDB;
using SpacetimeDB::Public;
using SpacetimeDB::Private;
// =============================================================================
// C++ Module Test - Equivalent to Rust module-test
// =============================================================================
//
// This module provides equivalence with the Rust module-test:
// - Table definitions with constraints and indexes
// - Support types and enums
// - Reducers for testing various database operations
// =============================================================================
// struct CircularA {
// uint32_t id;
// std::vector<CircularA> circ_ref; // References to CircularB by id
// };
// SPACETIMEDB_STRUCT(CircularA, id, circ_ref)
// SPACETIMEDB_TABLE(CircularA, circular_a, Public)
// =============================================================================
// SUPPORT TYPES AND ENUMS
// =============================================================================
// TestB struct - simple struct with a string field
struct TestB {
std::string foo;
};
SPACETIMEDB_STRUCT(TestB, foo)
// TestC enum - simple enum without payloads (with Namespace scope)
SPACETIMEDB_ENUM(TestC, Foo, Bar)
SPACETIMEDB_NAMESPACE(TestC, "Namespace")
// Workaround: C++ std::variant can't have duplicate types, so we create unique empty types
// for each unit variant instead of using Unit multiple times (like C# SDK does)
SPACETIMEDB_UNIT_TYPE(TestFFoo)
SPACETIMEDB_UNIT_TYPE(TestFBar)
// TestF enum - variant enum matching Rust: Foo, Bar, Baz(String)
SPACETIMEDB_ENUM(TestF,
(Foo, TestFFoo),
(Bar, TestFBar),
(Baz, std::string)
)
SPACETIMEDB_NAMESPACE(TestF, "Namespace")
// Baz struct
struct Baz {
std::string field;
};
SPACETIMEDB_STRUCT(Baz, field)
// Foobar enum helper type for unit variant
SPACETIMEDB_UNIT_TYPE(FoobarBar)
// Foobar enum - variant enum with payloads matching Rust: Baz(Baz), Bar, Har(u32)
SPACETIMEDB_ENUM(Foobar,
(Baz, Baz),
(Bar, FoobarBar),
(Har, uint32_t)
)
// =============================================================================
// TABLE DEFINITIONS
// =============================================================================
// Person table - public table with auto-increment primary key and age index
// Matches Rust: index(name = age, btree(columns = [age]))
struct Person {
uint32_t id;
std::string name;
uint8_t age;
};
SPACETIMEDB_STRUCT(Person, id, name, age)
SPACETIMEDB_TABLE(Person, person, Public)
FIELD_PrimaryKeyAutoInc(person, id)
FIELD_Index(person, age)
// RemoveTable - table for migration testing (can be removed)
struct RemoveTable {
uint32_t id;
};
SPACETIMEDB_STRUCT(RemoveTable, id)
SPACETIMEDB_TABLE(RemoveTable, table_to_remove, Private)
// TestA table - private table with foo index on x column
// Matches Rust: index(name = foo, btree(columns = [x]))
struct TestA {
uint32_t x;
uint32_t y;
std::string z;
};
SPACETIMEDB_STRUCT(TestA, x, y, z)
SPACETIMEDB_TABLE(TestA, test_a, Private)
// Note: Single column named indexes aren't supported - use regular index
FIELD_Index(test_a, x)
// Type alias for TestA
using TestAlias = TestA;
// TestD table - public table with optional TestC field
struct TestD {
std::optional<TestC> test_c;
TestF test_f; // Add TestF field to ensure it gets registered
};
SPACETIMEDB_STRUCT(TestD, test_c, test_f)
SPACETIMEDB_TABLE(TestD, test_d, Public)
// TestE table - private table with auto-increment primary key and btree index on name
// Matches Rust: #[index(btree)] on name field
struct TestE {
uint64_t id;
std::string name;
};
SPACETIMEDB_STRUCT(TestE, id, name)
SPACETIMEDB_TABLE(TestE, test_e, Private)
FIELD_PrimaryKeyAutoInc(test_e, id)
FIELD_Index(test_e, name)
// TestFoobar table - public table with Foobar enum field
struct TestFoobar {
Foobar field;
};
SPACETIMEDB_STRUCT(TestFoobar, field)
SPACETIMEDB_TABLE(TestFoobar, test_f, Public)
// PrivateTable - explicitly private table
struct PrivateTable {
std::string name;
};
SPACETIMEDB_STRUCT(PrivateTable, name)
SPACETIMEDB_TABLE(PrivateTable, private_table, Private)
// Point table - private table with two coordinates and multi-column index
// Rust has: index(name = multi_column_index, btree(columns = [x, y]))
struct Point {
int64_t x;
int64_t y;
};
SPACETIMEDB_STRUCT(Point, x, y)
SPACETIMEDB_TABLE(Point, points, Private)
// FIELD_NamedMultiColumnIndex(points, multi_column_index, x, y)
// PkMultiIdentity - table with multiple constraints
struct PkMultiIdentity {
uint32_t id;
uint32_t other;
};
SPACETIMEDB_STRUCT(PkMultiIdentity, id, other)
SPACETIMEDB_TABLE(PkMultiIdentity, pk_multi_identity, Private)
FIELD_PrimaryKey(pk_multi_identity, id)
FIELD_UniqueAutoInc(pk_multi_identity, other)
// RepeatingTestArg - table for scheduled reducer
struct RepeatingTestArg {
uint64_t scheduled_id;
ScheduleAt scheduled_at;
Timestamp prev_time;
};
SPACETIMEDB_STRUCT(RepeatingTestArg, scheduled_id, scheduled_at, prev_time)
SPACETIMEDB_TABLE(RepeatingTestArg, repeating_test_arg, Private)
FIELD_PrimaryKeyAutoInc(repeating_test_arg, scheduled_id)
SPACETIMEDB_SCHEDULE(repeating_test_arg, 1, repeating_test)
// HasSpecialStuff - table with special types
struct HasSpecialStuff {
Identity identity;
ConnectionId connection_id;
};
SPACETIMEDB_STRUCT(HasSpecialStuff, identity, connection_id)
SPACETIMEDB_TABLE(HasSpecialStuff, has_special_stuff, Private)
// Player table
struct Player {
Identity identity;
uint64_t player_id;
std::string name;
};
SPACETIMEDB_STRUCT(Player, identity, player_id, name)
SPACETIMEDB_TABLE(Player, player, Public)
FIELD_PrimaryKey(player, identity)
FIELD_UniqueAutoInc(player, player_id)
FIELD_Unique(player, name)
SPACETIMEDB_TABLE(Player, logged_out_player, Public)
FIELD_PrimaryKey(logged_out_player, identity)
FIELD_UniqueAutoInc(logged_out_player, player_id)
FIELD_Unique(logged_out_player, name)
// TableWithDefaults - test table with default values
struct TableWithDefaults {
uint32_t id;
std::string name;
uint32_t score;
bool active;
};
SPACETIMEDB_STRUCT(TableWithDefaults, id, name, score, active)
SPACETIMEDB_TABLE(TableWithDefaults, table_with_defaults, Public)
FIELD_PrimaryKeyAutoInc(table_with_defaults, id)
FIELD_Default(table_with_defaults, score, uint32_t(100))
FIELD_Default(table_with_defaults, active, true)
// =============================================================================
// VIEWS
// =============================================================================
// View to find the player associated with the calling identity
SPACETIMEDB_VIEW(std::optional<Player>, my_player, Public, ViewContext ctx) {
return ctx.db[player_identity].find(ctx.sender());
}
// =============================================================================
// REDUCERS
// =============================================================================
// Init reducer - called when module is first published
// COMMENTED OUT FOR DEBUGGING
SPACETIMEDB_INIT(init, ReducerContext ctx) {
RepeatingTestArg arg{
0, // scheduled_id
ScheduleAt(TimeDuration::from_millis(1000)),
ctx.timestamp
};
//ctx.db[repeating_test_arg].insert(arg);
return Ok();
}
// Repeating test reducer for scheduled operations
SPACETIMEDB_REDUCER(repeating_test, ReducerContext ctx, RepeatingTestArg arg) {
// Log would show delta time since last run
// In C++ we don't have log::trace equivalent yet
auto delta_time = ctx.timestamp.duration_since(arg.prev_time);
LOG_TRACE("Timestamp: " + ctx.timestamp.to_string() + " Delta time: " + delta_time.to_string());
return Ok();
}
// Add a person to the Person table
SPACETIMEDB_REDUCER(add, ReducerContext ctx, std::string name, uint8_t age) {
Person p{0, name, age}; // id will be auto-incremented
Person inserted = ctx.db[person].insert(p);
//LOG_INFO("Inserted person with auto-generated ID: " + std::to_string(inserted.id));
return Ok();
}
// Say hello to all persons
SPACETIMEDB_REDUCER(say_hello, ReducerContext ctx) {
// In Rust this logs "Hello, {name}!" for each person
for (const auto& p : ctx.db[person]) {
LOG_INFO("Hello, " + p.name + "!");
}
LOG_INFO("Hello, World!");
return Ok();
}
// List persons over a certain age - showcases range query functionality
SPACETIMEDB_REDUCER(list_over_age, ReducerContext ctx, uint8_t age) {
// Use index-based filtering with range queries - equivalent to Rust: ctx.db.person().age().filter(age..)
auto age_range = range_from(age);
// Use the indexed field accessor for efficient filtering
// ctx.db[person_age] creates a TypedIndexedAccessor with filter methods
auto filtered_persons = ctx.db[person_age].filter(age_range);
for (const auto& person : filtered_persons) {
LOG_INFO(person.name + " has age " + std::to_string(person.age) + " >= " + std::to_string(age));
}
return Ok();
}
// Log module identity
SPACETIMEDB_REDUCER(log_module_identity, ReducerContext ctx) {
LOG_INFO("Module identity: " + ctx.identity().to_string());
return Ok();
}
// Complex test reducer with multiple parameters
SPACETIMEDB_REDUCER(test, ReducerContext ctx, TestAlias arg, TestB arg2, TestC arg3, TestF arg4) {
LOG_INFO("BEGIN");
LOG_INFO("sender: " + ctx.sender().to_string());
LOG_INFO("timestamp: " + ctx.timestamp.to_string());
LOG_INFO("bar: " + arg2.foo);
// Match TestC enum
switch (arg3) {
case TestC::Foo:
LOG_INFO("Foo");
break;
case TestC::Bar:
LOG_INFO("Bar");
break;
}
// Match TestF variant enum
switch (arg4.index()) {
case 0: // Foo
LOG_INFO("Foo");
break;
case 1: // Bar
LOG_INFO("Bar");
break;
case 2: // Baz
LOG_INFO(std::get<std::string>(arg4.value));
break;
}
// Insert test data
for (uint32_t i = 0; i < 1000; ++i) {
TestA test_a_instance{i + arg.x, i + arg.y, "Yo"};
ctx.db[test_a].insert(test_a_instance);
}
// Count rows before delete
uint64_t row_count_before_delete = ctx.db[test_a].count();
LOG_INFO("Row count before delete: " + std::to_string(row_count_before_delete));
// Delete rows where x is between 5 and 10
uint32_t num_deleted = 0;
for (uint32_t row = 5; row < 10; ++row) {
auto to_delete = ctx.db[test_a_x].filter(row);
for (const auto& test_row : to_delete) {
LOG_INFO("Deleting row with x=" + std::to_string(test_row.x) + " y=" + std::to_string(test_row.y));
if (ctx.db[test_a].delete_by_value(test_row)) {
}
}
num_deleted++;
}
// Count rows after delete
uint64_t row_count_after_delete = ctx.db[test_a].count();
// Verify deletion worked correctly
if (row_count_before_delete != row_count_after_delete + num_deleted) {
LOG_ERROR("Started with " + std::to_string(row_count_before_delete) +
" rows, deleted " + std::to_string(num_deleted) +
", and wound up with " + std::to_string(row_count_after_delete) +
" rows... huh?");
}
// Test TestE insertion - using regular insert since try_insert isn't available in TableAccessor
TestE test_e_instance{0, "Tyler"};
TestE inserted = ctx.db[test_e].insert(test_e_instance);
LOG_INFO("Inserted: id=" + std::to_string(inserted.id) + " name=" + inserted.name);
LOG_INFO("Row count after delete: " + std::to_string(row_count_after_delete));
// Count all rows
uint64_t other_row_count = ctx.db[test_a].count();
LOG_INFO("Row count filtered by condition: " + std::to_string(other_row_count));
LOG_INFO("MultiColumn");
// Insert points for multi-column index testing
for (int64_t i = 0; i < 1000; ++i) {
Point point{i + static_cast<int64_t>(arg.x), i + static_cast<int64_t>(arg.y)};
ctx.db[points].insert(point);
}
// Count points with multi-column condition - diagnostic version
uint64_t multi_row_count = 0;
for (const auto& point : ctx.db[points]) {
if (point.x >= 0 && point.y <= 200) {
multi_row_count++;
}
}
LOG_INFO("Row count filtered by multi-column condition: " + std::to_string(multi_row_count));
LOG_INFO("END");
return Ok();
}
// Add a player (TestE entry)
SPACETIMEDB_REDUCER(add_player, ReducerContext ctx, std::string name) {
// Try without specifying id at all - but TestE struct requires both fields
// So we have to use 0 as placeholder
TestE player{0, name}; // id will be auto-incremented
TestE inserted = ctx.db[test_e_id].try_insert_or_update(player);
LOG_INFO("Inserted player with auto-generated ID: " + std::to_string(inserted.id));
ctx.db[test_e_id].try_insert_or_update(inserted);
LOG_INFO("Updated player after insert-or-update");
return Ok();
}
// Delete a player by ID
SPACETIMEDB_REDUCER(delete_player, ReducerContext ctx, uint64_t id) {
// Delete TestE entry with given id
// In C++ we'd need to implement delete by primary key
if (ctx.db[test_e_id].delete_by_key(id)) {
LOG_INFO("Deleted player with ID: " + std::to_string(id));
} else {
LOG_ERROR("No player found with ID: " + std::to_string(id));
}
return Ok();
}
// Delete players by name
SPACETIMEDB_REDUCER(delete_players_by_name, ReducerContext ctx, std::string name) {
// Delete all TestE entries with given name
// In C++ we'd iterate and delete matching entries
//auto to_delete = ctx.db[test_e_name].filter(name);
auto deleted = ctx.db[test_e_name].delete_by_value(name);
LOG_INFO("Deleted " + std::to_string(deleted) + " players with name: " + name);
return Ok();
}
// Client connected lifecycle reducer
SPACETIMEDB_CLIENT_CONNECTED(client_connected, ReducerContext ctx) {
// Called when a client connects
return Ok();
}
// Add entry to private table
SPACETIMEDB_REDUCER(add_private, ReducerContext ctx, std::string name) {
PrivateTable entry{name};
auto secret_entry = ctx.db[private_table].insert(entry);
LOG_INFO("Inserted private table entry: " + secret_entry.name);
return Ok();
}
// Query private table
SPACETIMEDB_REDUCER(query_private, ReducerContext ctx) {
// Iterate over private_table entries
// Would log each entry's name
for (const auto& entry : ctx.db[private_table]) {
LOG_INFO("Private, " + entry.name + "!");
}
LOG_INFO("Private, World!");
return Ok();
}
// Test btree index arguments - comprehensive range query testing
SPACETIMEDB_REDUCER(test_btree_index_args, ReducerContext ctx) {
// This tests various range query patterns equivalent to Rust's comprehensive index testing
// ==================================================================
// Single-column range queries on Person.age (uint8_t indexed field)
// ==================================================================
LOG_INFO("=== Testing age range queries ===");
// Test all range construction patterns
auto range_from_25 = range_from(uint8_t(25)); // 25..
auto range_to_30 = range_to(uint8_t(30)); // ..30
auto range_25_to_30 = range(uint8_t(25), uint8_t(30)); // 25..30
auto range_25_to_30_inc = range_inclusive(uint8_t(25), uint8_t(30)); // 25..=30
auto range_to_30_inc = range_to_inclusive(uint8_t(30)); // ..=30
auto range_all = range_full<uint8_t>(); // ..
// Count matches for each range pattern using INDEX-BASED FILTERING
// Now using ctx.db[person_age] indexed field accessor for efficient queries
size_t count_25_plus = ctx.db[person_age].filter(range_from_25).size();
size_t count_under_30 = ctx.db[person_age].filter(range_to_30).size();
size_t count_25_to_30 = ctx.db[person_age].filter(range_25_to_30).size();
size_t count_25_to_30_inc = ctx.db[person_age].filter(range_25_to_30_inc).size();
size_t count_under_30_inc = ctx.db[person_age].filter(range_to_30_inc).size();
size_t count_all = ctx.db[person_age].filter(range_all).size();
LOG_INFO("Age >= 25: " + std::to_string(count_25_plus));
LOG_INFO("Age < 30: " + std::to_string(count_under_30));
LOG_INFO("Age 25..30: " + std::to_string(count_25_to_30));
LOG_INFO("Age 25..=30: " + std::to_string(count_25_to_30_inc));
LOG_INFO("Age ..=30: " + std::to_string(count_under_30_inc));
LOG_INFO("All ages: " + std::to_string(count_all));
// ==================================================================
// Multi-column range queries on Point.x, Point.y (int64_t fields)
// Equivalent to Rust's multi_column_index filter tests
// ==================================================================
LOG_INFO("=== Testing coordinate range queries ===");
// Test coordinate-based ranges
auto x_range_positive = range_from(int64_t(0)); // x >= 0
auto x_range_0_to_100 = range(int64_t(0), int64_t(100)); // 0 <= x < 100
auto xy_combined = range_inclusive(int64_t(-50), int64_t(50)); // -50 <= coord <= 50
size_t positive_x_count = 0, x_0_to_100_count = 0, xy_in_range_count = 0;
for (const auto& point : ctx.db[points]) {
if (x_range_positive.contains(point.x)) positive_x_count++;
if (x_range_0_to_100.contains(point.x)) x_0_to_100_count++;
if (xy_combined.contains(point.x) && xy_combined.contains(point.y)) {
xy_in_range_count++;
}
}
LOG_INFO("Points with x >= 0: " + std::to_string(positive_x_count));
LOG_INFO("Points with 0 <= x < 100: " + std::to_string(x_0_to_100_count));
LOG_INFO("Points with x,y in [-50,50]: " + std::to_string(xy_in_range_count));
// ==================================================================
// String range queries on TestE.name (string indexed field)
// ==================================================================
LOG_INFO("=== Testing string range queries ===");
// String range examples - using INDEX-BASED FILTERING
auto name_range_a_to_m = range(std::string("A"), std::string("M")); // Names starting A-L
auto name_range_from_t = range_from(std::string("T")); // Names starting T and later
// Use ctx.db[test_e_name] indexed field accessor for efficient string range queries
size_t names_a_to_m = ctx.db[test_e_name].filter(name_range_a_to_m).size();
size_t names_from_t = ctx.db[test_e_name].filter(name_range_from_t).size();
LOG_INFO("Names A-L: " + std::to_string(names_a_to_m));
LOG_INFO("Names T+: " + std::to_string(names_from_t));
// ==================================================================
// Range query performance comparison
// ==================================================================
LOG_INFO("=== Range vs Manual Filtering Comparison ===");
auto performance_range = range_inclusive(uint8_t(20), uint8_t(40));
size_t range_matches = 0, manual_matches = 0;
// Method 1: Range-based filtering
for (const auto& p : ctx.db[person]) {
if (performance_range.contains(p.age)) {
range_matches++;
}
}
// Method 2: Manual filtering
for (const auto& p : ctx.db[person]) {
if (p.age >= 20 && p.age <= 40) {
manual_matches++;
}
}
LOG_INFO("Range-based matches: " + std::to_string(range_matches));
LOG_INFO("Manual matches: " + std::to_string(manual_matches));
LOG_INFO("Results match: " + std::to_string(range_matches == manual_matches ? 1 : 0));
return Ok();
}
// Test reducer for assertions
SPACETIMEDB_REDUCER(assert_caller_identity_is_module_identity, ReducerContext ctx) {
LOG_INFO("Sender: " + ctx.sender().to_string() + " Identity: " + ctx.identity().to_string());
if (ctx.sender() != ctx.identity()) {
LOG_ERROR("Assertion failed: caller identity does not match module identity");
} else {
LOG_INFO("Assertion passed: caller identity matches module identity");
}
return Ok();
}
SPACETIMEDB_REDUCER(test_defaults, ReducerContext ctx) {
LOG_INFO("=== Testing default values ===");
// Insert entries to test default value registration
// Note: In C++, we still need to provide values in the struct constructor,
// but the defaults are registered in the module metadata for use in migrations
// and when columns are added to existing tables
TableWithDefaults entry1{0, "Susan", 100, true}; // Using default values
auto inserted1 = ctx.db[table_with_defaults].insert(entry1);
LOG_INFO("Inserted: id=" + std::to_string(inserted1.id) +
" name=" + inserted1.name +
" score=" + std::to_string(inserted1.score) +
" active=" + std::to_string(inserted1.active));
TableWithDefaults entry2{0, "Charlie", 200, false}; // Using custom values
auto inserted2 = ctx.db[table_with_defaults].insert(entry2);
LOG_INFO("Inserted: id=" + std::to_string(inserted2.id) +
" name=" + inserted2.name +
" score=" + std::to_string(inserted2.score) +
" active=" + std::to_string(inserted2.active));
// Count total entries
size_t count = ctx.db[table_with_defaults].count();
LOG_INFO("Total entries with defaults: " + std::to_string(count));
LOG_INFO("Default values registered in module metadata");
return Ok();
}
SPACETIMEDB_REDUCER(throw_error, ReducerContext ctx) {
LOG_INFO("This reducer will throw an error.");
return Err("Intentional error from throw_error reducer.");
//return Ok();
}
// =============================================================================
// JWT AUTHENTICATION TESTS
// =============================================================================
// Test reducer that checks JWT authentication
SPACETIMEDB_REDUCER(test_jwt_auth, ReducerContext ctx) {
LOG_INFO("=== Testing JWT Authentication ===");
// Get sender_auth via the accessor method (matches Rust pattern)
const auto& auth = ctx.sender_auth();
// Check if JWT is present
if (auth.has_jwt()) {
LOG_INFO("JWT is present");
// Get the JWT and parse claims (returns optional)
auto jwt_opt = auth.get_jwt();
if (!jwt_opt.has_value()) {
LOG_ERROR("has_jwt() was true but get_jwt() returned empty");
return Ok();
}
auto& jwt = jwt_opt.value();
// Test claim accessors
auto subject = jwt.subject();
auto issuer = jwt.issuer();
auto audience = jwt.audience();
LOG_INFO("JWT Subject: " + subject);
LOG_INFO("JWT Issuer: " + issuer);
// Audience is a vector of strings
if (!audience.empty()) {
LOG_INFO("JWT Audience (first): " + audience[0]);
} else {
LOG_INFO("JWT Audience: (empty)");
}
// Test identity access
auto identity = jwt.get_identity();
LOG_INFO("JWT Identity: " + identity.to_string());
// Compare with caller identity
LOG_INFO("Caller Identity: " + ctx.sender().to_string());
// Verify that get_caller_identity returns the same as ctx.sender()
auto caller_identity = auth.get_caller_identity();
if (caller_identity == ctx.sender()) {
LOG_INFO("get_caller_identity matches ctx.sender()");
} else {
LOG_ERROR("get_caller_identity does NOT match ctx.sender()");
}
} else {
LOG_INFO("No JWT present (anonymous or scheduled reducer)");
// Verify get_caller_identity still works
auto caller_identity = auth.get_caller_identity();
LOG_INFO("Caller Identity (no JWT): " + caller_identity.to_string());
}
LOG_INFO("=== JWT Authentication Test Complete ===");
return Ok();
}
// =============================================================================
// PROCEDURES
// =============================================================================
SPACETIMEDB_PROCEDURE(Unit, sleep_one_second, ProcedureContext ctx) {
Timestamp prev = ctx.timestamp;
auto delta = TimeDuration::from_seconds(1);
Timestamp next = prev + delta; // assumes operator+ is available
LOG_INFO("Slept from " + prev.to_string() + " to " + next.to_string() +
", a total of " + delta.to_string());
return Unit{};
}
// Return a Baz struct with the foo parameter as a string
SPACETIMEDB_PROCEDURE(Baz, return_value, ProcedureContext ctx, uint64_t foo) {
return Baz{std::to_string(foo)};
}
// Execute say_hello reducer within a transaction context
SPACETIMEDB_PROCEDURE(Unit, with_tx, ProcedureContext ctx) {
ctx.with_tx([](TxContext& tx) {
// Call say_hello logic within transaction
for (const auto& p : tx.db[person]) {
LOG_INFO("Hello, " + p.name + "!");
}
LOG_INFO("Hello, World!");
});
return Unit{};
}
// Hit SpacetimeDB's schema HTTP route and return its result as a string
SPACETIMEDB_PROCEDURE(std::string, get_my_schema_via_http, ProcedureContext ctx) {
Identity module_identity = ctx.identity();
std::string url = "http://localhost:3000/v1/database/" + module_identity.to_string() + "/schema?version=9";
auto result = ctx.http.get(url);
if (result.is_ok()) {
return result.value().body.to_string_utf8_lossy();
} else {
return result.error();
}
}