Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions tests/test_wirelog_advanced.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ static const char *PROG_SRC =
"path(X,Y) :- edge(X,Y).\n"
"path(X,Z) :- path(X,Y), edge(Y,Z).\n";

static const char *RELATION_NAME_LIFETIME_SRC
= ".decl edge(x: int64, y: int64)\n"
".decl reach(x: int64, y: int64)\n"
"reach(X, Y) :- edge(X, Y).\n";

static const char *PROG_RBAC_SRC =
".decl role_permission(role:symbol,perm:symbol)\n"
".decl member_of(user:symbol,role:symbol,scope:symbol)\n"
Expand Down Expand Up @@ -382,6 +387,158 @@ test_insert_step_delta(void)
return rc;
}

/* Parity (#931): mirrors test_wirelog_easy.c::test_relation_name_lifetime. */
static int
test_relation_name_lifetime(void)
{
int rc = 0;
int64_t row[2] = { 1, 2 };
wirelog_program_t *prog
= parse_or_die(RELATION_NAME_LIFETIME_SRC, "relation-name lifetime");
if (!prog)
return 1;

wirelog_session_t *s = NULL;
if (wirelog_session_create(prog, WIRELOG_BACKEND_DEFAULT, 1, &s)
!= WIRELOG_OK) {
wirelog_program_free(prog);
return 1;
}

struct tuple_filter tuples = { .target_relation = "reach" };
if (wirelog_session_snapshot(s, filter_tuples, &tuples) != WIRELOG_OK) {
fprintf(stderr, "relation-name lifetime: baseline snapshot failed\n");
rc = 1;
goto regular_out;
}

char regular_relation[8] = "edge";
if (wirelog_session_insert(s, regular_relation, row, 1, 2)
!= WIRELOG_OK) {
fprintf(stderr, "relation-name lifetime: regular insert failed\n");
rc = 1;
goto regular_out;
}
strcpy(regular_relation, "bogus");

memset(&tuples, 0, sizeof(tuples));
tuples.target_relation = "reach";
if (wirelog_session_snapshot(s, filter_tuples, &tuples) != WIRELOG_OK) {
fprintf(stderr,
"relation-name lifetime: post-insert snapshot failed\n");
rc = 1;
goto regular_out;
}
bool found_snapshot = false;
for (int i = 0; i < tuples.count; i++) {
if (tuples.ncols[i] == 2 && tuples.rows[i][0] == row[0]
&& tuples.rows[i][1] == row[1]) {
found_snapshot = true;
break;
}
}
if (!found_snapshot) {
fprintf(stderr,
"relation-name lifetime: mutable regular name suppressed "
"reach(1,2)\n");
rc = 1;
}

regular_out:
wirelog_session_destroy(s);
wirelog_program_free(prog);
if (rc != 0)
return rc;

prog = parse_or_die(
RELATION_NAME_LIFETIME_SRC, "relation-name delta lifetime");
if (!prog)
return 1;
s = NULL;
if (wirelog_session_create(prog, WIRELOG_BACKEND_DEFAULT, 1, &s)
!= WIRELOG_OK) {
wirelog_program_free(prog);
return 1;
}

struct delta_collector deltas;
memset(&deltas, 0, sizeof(deltas));
if (wirelog_session_set_delta_cb(s, collect_delta_rows, &deltas)
!= WIRELOG_OK) {
fprintf(stderr, "relation-name lifetime: set_delta_cb failed\n");
rc = 1;
goto delta_out;
}

char inserted_relation[8] = "edge";
if (wirelog_session_insert(s, inserted_relation, row, 1, 2)
!= WIRELOG_OK) {
fprintf(stderr, "relation-name lifetime: delta insert failed\n");
rc = 1;
goto delta_out;
}
strcpy(inserted_relation, "bogus");
if (wirelog_session_step(s) != WIRELOG_OK) {
fprintf(stderr,
"relation-name lifetime: post-insert step failed\n");
rc = 1;
goto delta_out;
}

bool found_insert = false;
for (int i = 0; i < deltas.count; i++) {
if (strcmp(deltas.relations[i], "reach") == 0
&& deltas.ncols[i] == 2 && deltas.rows[i][0] == row[0]
&& deltas.rows[i][1] == row[1] && deltas.diffs[i] == 1) {
found_insert = true;
break;
}
}
int before_remove = deltas.count;

char removed_relation[8] = "edge";
if (wirelog_session_remove(s, removed_relation, row, 1, 2)
!= WIRELOG_OK) {
fprintf(stderr, "relation-name lifetime: delta remove failed\n");
rc = 1;
goto delta_out;
}
strcpy(removed_relation, "bogus");
if (wirelog_session_step(s) != WIRELOG_OK) {
fprintf(stderr,
"relation-name lifetime: post-remove step failed\n");
rc = 1;
goto delta_out;
}

bool found_remove = false;
for (int i = before_remove; i < deltas.count; i++) {
if (strcmp(deltas.relations[i], "reach") == 0
&& deltas.ncols[i] == 2 && deltas.rows[i][0] == row[0]
&& deltas.rows[i][1] == row[1] && deltas.diffs[i] == -1) {
found_remove = true;
break;
}
}
if (!found_insert) {
fprintf(stderr,
"relation-name lifetime: mutable inserted name suppressed "
"+reach(1,2)\n");
rc = 1;
}
if (!found_remove) {
fprintf(stderr,
"relation-name lifetime: mutable removed name suppressed "
"-reach(1,2)\n");
rc = 1;
}

delta_out:
wirelog_session_destroy(s);
wirelog_program_free(prog);
return rc;
}

/* T6: insert / remove pair leaves the relation back at zero. */
static int
test_insert_remove_roundtrip(void)
Expand Down Expand Up @@ -1617,6 +1774,7 @@ main(void)
failures += test_create_invalid_backend();
failures += test_inline_facts_seeded();
failures += test_insert_step_delta();
failures += test_relation_name_lifetime();
failures += test_insert_remove_roundtrip();
failures += test_null_safety();
failures += test_open_parse_error();
Expand Down
131 changes: 131 additions & 0 deletions tests/test_wirelog_easy.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ static const char *ACCESS_CONTROL_SRC
".decl granted(user: symbol, perm: symbol)\n"
"granted(U, P) :- can(U, P).\n";

static const char *RELATION_NAME_LIFETIME_SRC
= ".decl edge(x: int64, y: int64)\n"
".decl reach(x: int64, y: int64)\n"
"reach(X, Y) :- edge(X, Y).\n";

/* ======================================================================== */
/* Delta Collector */
/* ======================================================================== */
Expand Down Expand Up @@ -622,6 +627,131 @@ test_eager_sessions_preintern_projection_literals(void)
PASS();
}

/* PARITY: paired in test_wirelog_advanced.c by the same test name (#931). */
static void
test_relation_name_lifetime(void)
{
TEST("insert/remove retain relation name until evaluation");

int64_t row[2] = { 1, 2 };
wirelog_easy_session_t *s = NULL;
if (wirelog_easy_open(RELATION_NAME_LIFETIME_SRC, &s) != WIRELOG_OK
|| !s) {
FAIL("regular-path open failed");
return;
}

tuple_collector_t tuples;
memset(&tuples, 0, sizeof(tuples));
if (wirelog_easy_snapshot(s, "reach", collect_tuple, &tuples)
!= WIRELOG_OK) {
FAIL("baseline snapshot failed");
wirelog_easy_close(s);
return;
}

char regular_relation[8] = "edge";
if (wirelog_easy_insert(s, regular_relation, row, 2) != WIRELOG_OK) {
FAIL("regular-path insert failed");
wirelog_easy_close(s);
return;
}
strcpy(regular_relation, "bogus");

memset(&tuples, 0, sizeof(tuples));
if (wirelog_easy_snapshot(s, "reach", collect_tuple, &tuples)
!= WIRELOG_OK) {
FAIL("snapshot after mutable-name insert failed");
wirelog_easy_close(s);
return;
}
bool found_snapshot = false;
for (int i = 0; i < tuples.count; i++) {
if (strcmp(tuples.relations[i], "reach") == 0
&& tuples.ncols[i] == 2 && tuples.rows[i][0] == row[0]
&& tuples.rows[i][1] == row[1]) {
found_snapshot = true;
break;
}
}
wirelog_easy_close(s);
if (!found_snapshot) {
FAIL("mutable regular relation name suppressed reach(1,2)");
return;
}

s = NULL;
if (wirelog_easy_open(RELATION_NAME_LIFETIME_SRC, &s) != WIRELOG_OK
|| !s) {
FAIL("delta-path open failed");
return;
}
delta_collector_t deltas;
memset(&deltas, 0, sizeof(deltas));
if (wirelog_easy_set_delta_cb(s, collect_delta, &deltas) != WIRELOG_OK) {
FAIL("set_delta_cb failed");
wirelog_easy_close(s);
return;
}

char inserted_relation[8] = "edge";
if (wirelog_easy_insert(s, inserted_relation, row, 2) != WIRELOG_OK) {
FAIL("delta-path insert failed");
wirelog_easy_close(s);
return;
}
strcpy(inserted_relation, "bogus");
if (wirelog_easy_step(s) != WIRELOG_OK) {
FAIL("step after mutable-name insert failed");
wirelog_easy_close(s);
return;
}

bool found_insert = false;
for (int i = 0; i < deltas.count; i++) {
if (strcmp(deltas.relations[i], "reach") == 0
&& deltas.ncols[i] == 2 && deltas.rows[i][0] == row[0]
&& deltas.rows[i][1] == row[1] && deltas.diffs[i] == 1) {
found_insert = true;
break;
}
}
int before_remove = deltas.count;

char removed_relation[8] = "edge";
if (wirelog_easy_remove(s, removed_relation, row, 2) != WIRELOG_OK) {
FAIL("delta-path remove failed");
wirelog_easy_close(s);
return;
}
strcpy(removed_relation, "bogus");
if (wirelog_easy_step(s) != WIRELOG_OK) {
FAIL("step after mutable-name remove failed");
wirelog_easy_close(s);
return;
}

bool found_remove = false;
for (int i = before_remove; i < deltas.count; i++) {
if (strcmp(deltas.relations[i], "reach") == 0
&& deltas.ncols[i] == 2 && deltas.rows[i][0] == row[0]
&& deltas.rows[i][1] == row[1] && deltas.diffs[i] == -1) {
found_remove = true;
break;
}
}
wirelog_easy_close(s);
if (!found_insert) {
FAIL("mutable inserted relation name suppressed +reach(1,2)");
return;
}
if (!found_remove) {
FAIL("mutable removed relation name suppressed -reach(1,2)");
return;
}
PASS();
}

static void
test_inline_compound_body_binding(void)
{
Expand Down Expand Up @@ -1999,6 +2129,7 @@ main(void)
test_intern_returns_same_id();
test_insert_step_delta();
test_eager_sessions_preintern_projection_literals();
test_relation_name_lifetime();
test_inline_compound_body_binding();
test_inline_compound_body_join_binding();
test_inline_compound_functor_mismatch_is_empty();
Expand Down
8 changes: 6 additions & 2 deletions wirelog/columnar/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -886,7 +886,9 @@ typedef struct wl_col_session_t {
bool tdd_decision_tracking_active;
/* Phase 4: tracks which relation was just inserted via
* col_session_insert_incremental, enables affected-stratum skip
* optimization. Borrowed pointer; lifetime: until next session_step.
* optimization. Session-owned canonical relation name; base and
* compound-side relations remain registered while input work is pending,
* so the pointer stays valid until a successful step/snapshot clears it.
* NULL when no incremental insert preceded the current step (all strata
* evaluated normally via affected_mask = UINT64_MAX). */
const char *last_inserted_relation;
Expand Down Expand Up @@ -938,7 +940,9 @@ typedef struct wl_col_session_t {
bool stratum_is_monotone[MAX_STRATA];
/* Retraction delta tracking (Issue #158): tracks which relation was just
* removed via col_session_remove_incremental, enables delta retraction path.
* Borrowed pointer; lifetime: until next session_step.
* Session-owned canonical relation name; the base relation remains
* registered while retraction work is pending, so the pointer stays valid
* until a successful step clears it.
* NULL when no incremental remove preceded the current step. */
const char *last_removed_relation;
/* Retraction-seeded incremental evaluation (Issue #158).
Expand Down
Loading
Loading