Skip to content

Commit d0bd83a

Browse files
Merge branch 'master' into jlarabie/money-exchange
2 parents da039bc + 0922704 commit d0bd83a

23 files changed

Lines changed: 856 additions & 240 deletions

File tree

.github/workflows/ci.yml

Lines changed: 0 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -878,151 +878,6 @@ jobs:
878878
exit 1
879879
}
880880
881-
internal-tests:
882-
name: Internal Tests
883-
needs: [lints]
884-
# Skip if not a PR or a push to master
885-
# Skip if this is an external contribution. GitHub secrets will be empty, so the step would fail anyway.
886-
if: ${{ (github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/master'))
887-
&& (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork) }}
888-
permissions:
889-
contents: read
890-
pull-requests: read
891-
runs-on: ubuntu-latest
892-
env:
893-
TARGET_OWNER: clockworklabs
894-
TARGET_REPO: SpacetimeDBPrivate
895-
steps:
896-
# Skip the private dispatch entirely when only `docs/` is touched. The job
897-
# itself still completes successfully so required-status-check gating is
898-
# satisfied without spending private-runner time on a docs-only change.
899-
- name: Detect non-docs changes
900-
id: filter
901-
uses: dorny/paths-filter@v3
902-
with:
903-
filters: |
904-
non_docs:
905-
- '!docs/**'
906-
907-
- id: dispatch
908-
name: Trigger tests
909-
if: steps.filter.outputs.non_docs == 'true'
910-
uses: actions/github-script@v7
911-
with:
912-
github-token: ${{ secrets.SPACETIMEDB_PRIVATE_TOKEN }}
913-
script: |
914-
const workflowId = 'ci.yml';
915-
const targetRef = 'master';
916-
const targetOwner = process.env.TARGET_OWNER;
917-
const targetRepo = process.env.TARGET_REPO;
918-
// Use the ref for pull requests because the head sha is brittle (github does some extra dance where it merges in master).
919-
const publicRef = (context.eventName === 'pull_request') ? context.payload.pull_request.head.ref : context.sha;
920-
const publicPrNumber = context.payload.pull_request?.number ?? context.payload.inputs?.pr_number;
921-
const preDispatch = new Date().toISOString();
922-
const inputs = { public_ref: publicRef };
923-
if (publicPrNumber) {
924-
inputs.public_pr_number = String(publicPrNumber);
925-
}
926-
927-
// Dispatch the workflow in the target repository
928-
await github.rest.actions.createWorkflowDispatch({
929-
owner: targetOwner,
930-
repo: targetRepo,
931-
workflow_id: workflowId,
932-
ref: targetRef,
933-
inputs,
934-
});
935-
936-
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
937-
938-
// Find the dispatched run by name
939-
let runId = null;
940-
for (let attempt = 0; attempt < 20 && !runId; attempt++) { // up to ~10 minutes to locate the run
941-
await sleep(5000);
942-
const runsResp = await github.rest.actions.listWorkflowRuns({
943-
owner: targetOwner,
944-
repo: targetRepo,
945-
workflow_id: workflowId,
946-
event: 'workflow_dispatch',
947-
branch: targetRef,
948-
per_page: 50,
949-
});
950-
951-
const expectedName = `CI [public_ref=${publicRef}]`;
952-
const candidates = runsResp.data.workflow_runs
953-
.filter(r => r.name === expectedName && new Date(r.created_at) >= new Date(preDispatch))
954-
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
955-
956-
if (candidates.length > 0) {
957-
runId = candidates[0].id;
958-
break;
959-
}
960-
}
961-
962-
if (!runId) {
963-
core.setFailed('Failed to locate dispatched run in the private repository.');
964-
return;
965-
}
966-
967-
const runUrl = `https://github.com/${targetOwner}/${targetRepo}/actions/runs/${runId}`;
968-
core.info(`View run: ${runUrl}`);
969-
core.setOutput('run_id', String(runId));
970-
core.setOutput('run_url', runUrl);
971-
972-
- name: Wait for Internal Tests to complete
973-
if: steps.filter.outputs.non_docs == 'true'
974-
uses: actions/github-script@v7
975-
with:
976-
github-token: ${{ secrets.SPACETIMEDB_PRIVATE_TOKEN }}
977-
script: |
978-
const targetOwner = process.env.TARGET_OWNER;
979-
const targetRepo = process.env.TARGET_REPO;
980-
const runId = Number(`${{ steps.dispatch.outputs.run_id }}`);
981-
const runUrl = `${{ steps.dispatch.outputs.run_url }}`;
982-
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
983-
984-
core.info(`Waiting for workflow result... ${runUrl}`);
985-
986-
let conclusion = null;
987-
for (let attempt = 0; attempt < 240; attempt++) { // up to ~2 hours
988-
const runResp = await github.rest.actions.getWorkflowRun({
989-
owner: targetOwner,
990-
repo: targetRepo,
991-
run_id: runId
992-
});
993-
const { status, conclusion: c } = runResp.data;
994-
if (status === 'completed') {
995-
conclusion = c || 'success';
996-
break;
997-
}
998-
await sleep(30000);
999-
}
1000-
1001-
if (!conclusion) {
1002-
core.setFailed('Timed out waiting for private workflow to complete.');
1003-
return;
1004-
}
1005-
1006-
if (conclusion !== 'success') {
1007-
core.setFailed(`Private workflow failed with conclusion: ${conclusion}`);
1008-
}
1009-
1010-
- name: Cancel invoked run if workflow cancelled
1011-
if: ${{ cancelled() && steps.dispatch.outputs.run_id }}
1012-
uses: actions/github-script@v7
1013-
with:
1014-
github-token: ${{ secrets.SPACETIMEDB_PRIVATE_TOKEN }}
1015-
script: |
1016-
const targetOwner = process.env.TARGET_OWNER;
1017-
const targetRepo = process.env.TARGET_REPO;
1018-
const runId = Number(`${{ steps.dispatch.outputs.run_id }}`);
1019-
if (!runId) return;
1020-
await github.rest.actions.cancelWorkflowRun({
1021-
owner: targetOwner,
1022-
repo: targetRepo,
1023-
run_id: runId,
1024-
});
1025-
1026881
global_json_policy:
1027882
name: Verify global.json files are symlinks
1028883
runs-on: ubuntu-latest
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
name: Internal Tests
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- master
8+
merge_group:
9+
workflow_dispatch:
10+
11+
run-name: Internal Tests [ref=${{ github.ref }}]
12+
13+
concurrency:
14+
group: ${{ github.workflow }}-${{ github.ref }}
15+
cancel-in-progress: true
16+
17+
permissions:
18+
contents: read
19+
pull-requests: read
20+
21+
jobs:
22+
internal-tests:
23+
name: Internal Tests
24+
# Skip if not a PR or a push to master
25+
# Skip if this is an external contribution. GitHub secrets will be empty, so the step would fail anyway.
26+
if: ${{ (github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/master'))
27+
&& (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork) }}
28+
runs-on: ubuntu-latest
29+
env:
30+
TARGET_OWNER: clockworklabs
31+
TARGET_REPO: SpacetimeDBPrivate
32+
steps:
33+
# Skip the private dispatch entirely when only `docs/` is touched. The job
34+
# itself still completes successfully so required-status-check gating is
35+
# satisfied without spending private-runner time on a docs-only change.
36+
- name: Detect non-docs changes
37+
id: filter
38+
uses: dorny/paths-filter@v3
39+
with:
40+
filters: |
41+
non_docs:
42+
- '!docs/**'
43+
44+
- id: dispatch
45+
name: Trigger tests
46+
if: steps.filter.outputs.non_docs == 'true'
47+
uses: actions/github-script@v7
48+
with:
49+
github-token: ${{ secrets.SPACETIMEDB_PRIVATE_TOKEN }}
50+
script: |
51+
const workflowId = 'ci.yml';
52+
const targetRef = 'master';
53+
const targetOwner = process.env.TARGET_OWNER;
54+
const targetRepo = process.env.TARGET_REPO;
55+
// Use the ref for pull requests because the head sha is brittle (github does some extra dance where it merges in master).
56+
const publicRef = (context.eventName === 'pull_request') ? context.payload.pull_request.head.ref : context.sha;
57+
const publicPrNumber = context.payload.pull_request?.number;
58+
const preDispatch = new Date().toISOString();
59+
const inputs = { public_ref: publicRef };
60+
if (publicPrNumber) {
61+
inputs.public_pr_number = String(publicPrNumber);
62+
}
63+
64+
// Dispatch the workflow in the target repository
65+
await github.rest.actions.createWorkflowDispatch({
66+
owner: targetOwner,
67+
repo: targetRepo,
68+
workflow_id: workflowId,
69+
ref: targetRef,
70+
inputs,
71+
});
72+
73+
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
74+
75+
// Find the dispatched run by name
76+
let runId = null;
77+
for (let attempt = 0; attempt < 20 && !runId; attempt++) { // up to ~10 minutes to locate the run
78+
await sleep(5000);
79+
const runsResp = await github.rest.actions.listWorkflowRuns({
80+
owner: targetOwner,
81+
repo: targetRepo,
82+
workflow_id: workflowId,
83+
event: 'workflow_dispatch',
84+
branch: targetRef,
85+
per_page: 50,
86+
});
87+
88+
const expectedName = `CI [public_ref=${publicRef}]`;
89+
const candidates = runsResp.data.workflow_runs
90+
.filter(r => r.name === expectedName && new Date(r.created_at) >= new Date(preDispatch))
91+
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
92+
93+
if (candidates.length > 0) {
94+
runId = candidates[0].id;
95+
break;
96+
}
97+
}
98+
99+
if (!runId) {
100+
core.setFailed('Failed to locate dispatched run in the private repository.');
101+
return;
102+
}
103+
104+
const runUrl = `https://github.com/${targetOwner}/${targetRepo}/actions/runs/${runId}`;
105+
core.info(`View run: ${runUrl}`);
106+
core.setOutput('run_id', String(runId));
107+
core.setOutput('run_url', runUrl);
108+
109+
- name: Wait for Internal Tests to complete
110+
if: steps.filter.outputs.non_docs == 'true'
111+
uses: actions/github-script@v7
112+
with:
113+
github-token: ${{ secrets.SPACETIMEDB_PRIVATE_TOKEN }}
114+
script: |
115+
const targetOwner = process.env.TARGET_OWNER;
116+
const targetRepo = process.env.TARGET_REPO;
117+
const runId = Number(`${{ steps.dispatch.outputs.run_id }}`);
118+
const runUrl = `${{ steps.dispatch.outputs.run_url }}`;
119+
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
120+
121+
core.info(`Waiting for workflow result... ${runUrl}`);
122+
123+
let conclusion = null;
124+
for (let attempt = 0; attempt < 240; attempt++) { // up to ~2 hours
125+
const runResp = await github.rest.actions.getWorkflowRun({
126+
owner: targetOwner,
127+
repo: targetRepo,
128+
run_id: runId
129+
});
130+
const { status, conclusion: c } = runResp.data;
131+
if (status === 'completed') {
132+
conclusion = c || 'success';
133+
break;
134+
}
135+
await sleep(30000);
136+
}
137+
138+
if (!conclusion) {
139+
core.setFailed('Timed out waiting for private workflow to complete.');
140+
return;
141+
}
142+
143+
if (conclusion !== 'success') {
144+
core.setFailed(`Private workflow failed with conclusion: ${conclusion}`);
145+
}
146+
147+
- name: Cancel invoked run if workflow cancelled
148+
if: ${{ cancelled() && steps.dispatch.outputs.run_id }}
149+
uses: actions/github-script@v7
150+
with:
151+
github-token: ${{ secrets.SPACETIMEDB_PRIVATE_TOKEN }}
152+
script: |
153+
const targetOwner = process.env.TARGET_OWNER;
154+
const targetRepo = process.env.TARGET_REPO;
155+
const runId = Number(`${{ steps.dispatch.outputs.run_id }}`);
156+
if (!runId) return;
157+
await github.rest.actions.cancelWorkflowRun({
158+
owner: targetOwner,
159+
repo: targetRepo,
160+
run_id: runId,
161+
});

crates/core/src/db/relational_db.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ use spacetimedb_datastore::locking_tx_datastore::datastore::TxMetrics;
1616
use spacetimedb_datastore::locking_tx_datastore::state_view::{
1717
IterByColEqMutTx, IterByColRangeMutTx, IterMutTx, StateView,
1818
};
19-
use spacetimedb_datastore::locking_tx_datastore::{ApplyHistoryCounters, IndexScanPointOrRange, MutTxId, TxId};
19+
use spacetimedb_datastore::locking_tx_datastore::{
20+
ApplyHistoryCounters, IndexScanPointOrRange, MutTxId, TxId, ViewCallInfo,
21+
};
2022
use spacetimedb_datastore::system_tables::{
2123
system_tables, StModuleRow, ST_CLIENT_ID, ST_CONNECTION_CREDENTIALS_ID, ST_VIEW_SUB_ID,
2224
};
@@ -1601,6 +1603,26 @@ impl RelationalDB {
16011603
Ok(())
16021604
}
16031605

1606+
/// Materialize a view call and replace its committed read set on transaction commit.
1607+
///
1608+
/// The read set is only marked for replacement after materialization succeeds. If the
1609+
/// transaction rolls back, the replacement marker rolls back with it.
1610+
pub fn materialize_view_call(
1611+
&self,
1612+
tx: &mut MutTxId,
1613+
table_id: TableId,
1614+
view_call: ViewCallInfo,
1615+
rows: Vec<ProductValue>,
1616+
) -> Result<(), DBError> {
1617+
match view_call.sender {
1618+
Some(sender) => self.materialize_view(tx, table_id, sender, rows)?,
1619+
None => self.materialize_anonymous_view(tx, table_id, rows)?,
1620+
}
1621+
tx.replace_view_read_set(view_call);
1622+
1623+
Ok(())
1624+
}
1625+
16041626
fn write_view_rows(
16051627
&self,
16061628
tx: &mut MutTxId,

0 commit comments

Comments
 (0)