1use anyhow::Result;
10use rusqlite::Connection;
11
12use super::TargetClass;
13
14pub const SCHEMA_VERSION: i64 = 3;
16
17pub const CORE_DDL: &str = "
19CREATE TABLE IF NOT EXISTS sessions (
20 id TEXT PRIMARY KEY,
21 kind TEXT NOT NULL CHECK (kind IN ('debug','profile')),
22 target TEXT NOT NULL,
23 target_class TEXT NOT NULL,
24 target_hash TEXT,
25 started_at TEXT NOT NULL,
26 ended_at TEXT,
27 label TEXT NOT NULL,
28 created_by TEXT NOT NULL DEFAULT 'auto'
29);
30
31CREATE TABLE IF NOT EXISTS layers (
32 id INTEGER PRIMARY KEY,
33 session_id TEXT NOT NULL REFERENCES sessions(id),
34 source TEXT NOT NULL,
35 file TEXT,
36 collected_at TEXT,
37 command_used TEXT,
38 collection_secs REAL,
39 target_hash TEXT
40);
41CREATE INDEX IF NOT EXISTS idx_layers_session ON layers(session_id);
42
43CREATE TABLE IF NOT EXISTS symbols (
44 id INTEGER PRIMARY KEY,
45 session_id TEXT NOT NULL REFERENCES sessions(id),
46 lang TEXT NOT NULL,
47 fqn TEXT NOT NULL,
48 file TEXT,
49 line INTEGER,
50 demangled TEXT,
51 raw TEXT NOT NULL,
52 is_synthetic INTEGER NOT NULL DEFAULT 0,
53 UNIQUE(session_id, lang, fqn)
54);
55CREATE INDEX IF NOT EXISTS idx_symbols_fqn ON symbols(lang, fqn);
56
57CREATE TABLE IF NOT EXISTS meta (
58 session_id TEXT NOT NULL REFERENCES sessions(id),
59 key TEXT NOT NULL,
60 value TEXT,
61 PRIMARY KEY (session_id, key)
62);
63
64CREATE TABLE IF NOT EXISTS failures (
65 session_id TEXT NOT NULL REFERENCES sessions(id),
66 phase TEXT,
67 error TEXT
68);
69
70CREATE TABLE IF NOT EXISTS regions (
71 id INTEGER PRIMARY KEY,
72 session_id TEXT NOT NULL REFERENCES sessions(id),
73 name TEXT NOT NULL,
74 start_us REAL,
75 duration_us REAL,
76 thread TEXT,
77 layer_id INTEGER REFERENCES layers(id)
78);
79CREATE INDEX IF NOT EXISTS idx_regions_session ON regions(session_id);
80
81CREATE TABLE IF NOT EXISTS allocations (
82 id INTEGER PRIMARY KEY,
83 session_id TEXT NOT NULL REFERENCES sessions(id),
84 op TEXT NOT NULL,
85 address INTEGER NOT NULL,
86 bytes INTEGER NOT NULL,
87 start_us REAL,
88 heap TEXT NOT NULL DEFAULT 'default',
89 thread TEXT,
90 stack_json TEXT,
91 layer_id INTEGER REFERENCES layers(id)
92);
93CREATE INDEX IF NOT EXISTS idx_alloc_addr ON allocations(session_id, address);
94CREATE INDEX IF NOT EXISTS idx_alloc_time ON allocations(session_id, start_us);
95";
96
97pub const DEBUG_DDL: &str = "
100CREATE TABLE IF NOT EXISTS commands (
101 seq INTEGER PRIMARY KEY,
102 session_id TEXT NOT NULL REFERENCES sessions(id),
103 input TEXT NOT NULL,
104 output_head TEXT,
105 output_file TEXT,
106 output_bytes INTEGER,
107 ts TEXT NOT NULL,
108 canonical_op TEXT
109);
110CREATE INDEX IF NOT EXISTS idx_commands_session ON commands(session_id);
111
112CREATE TABLE IF NOT EXISTS breakpoint_hits (
113 id INTEGER PRIMARY KEY,
114 session_id TEXT NOT NULL REFERENCES sessions(id),
115 location_key TEXT NOT NULL,
116 hit_seq INTEGER NOT NULL,
117 thread TEXT,
118 ts TEXT NOT NULL,
119 locals_json TEXT,
120 stack_json TEXT
121);
122CREATE INDEX IF NOT EXISTS idx_bp_location ON breakpoint_hits(session_id, location_key);
123
124CREATE TABLE IF NOT EXISTS watch_evals (
125 id INTEGER PRIMARY KEY,
126 session_id TEXT NOT NULL REFERENCES sessions(id),
127 hit_id INTEGER REFERENCES breakpoint_hits(id),
128 expr TEXT NOT NULL,
129 value TEXT,
130 type_name TEXT,
131 ts TEXT NOT NULL
132);
133";
134
135pub const CROSSTRACK_DDL: &str = "
138CREATE TABLE IF NOT EXISTS disassembly (
139 id INTEGER PRIMARY KEY,
140 session_id TEXT NOT NULL REFERENCES sessions(id),
141 symbol_id INTEGER REFERENCES symbols(id),
142 source TEXT NOT NULL,
143 tier TEXT,
144 code_bytes INTEGER,
145 asm_text TEXT NOT NULL,
146 asm_lines_json TEXT,
147 collected_at TEXT NOT NULL,
148 trigger TEXT,
149 layer_id INTEGER REFERENCES layers(id)
150);
151CREATE INDEX IF NOT EXISTS idx_disasm_sym ON disassembly(symbol_id);
152CREATE UNIQUE INDEX IF NOT EXISTS idx_disasm_identity
153 ON disassembly(session_id, symbol_id, source, COALESCE(tier, ''));
154
155CREATE TABLE IF NOT EXISTS source_snapshots (
156 id INTEGER PRIMARY KEY,
157 session_id TEXT NOT NULL REFERENCES sessions(id),
158 symbol_id INTEGER REFERENCES symbols(id),
159 file TEXT,
160 line_start INTEGER,
161 line_end INTEGER,
162 text TEXT,
163 content_hash TEXT,
164 collected_at TEXT NOT NULL
165);
166CREATE INDEX IF NOT EXISTS idx_src_sym ON source_snapshots(symbol_id);
167
168CREATE TABLE IF NOT EXISTS alloc_sites (
169 id INTEGER PRIMARY KEY,
170 session_id TEXT NOT NULL REFERENCES sessions(id),
171 symbol_id INTEGER REFERENCES symbols(id),
172 bytes_total INTEGER,
173 count INTEGER,
174 largest_bytes INTEGER,
175 collected_at TEXT NOT NULL,
176 layer_id INTEGER REFERENCES layers(id)
177);
178CREATE INDEX IF NOT EXISTS idx_alloc_site_sym ON alloc_sites(symbol_id);
179
180CREATE TABLE IF NOT EXISTS insn_hits (
181 id INTEGER PRIMARY KEY,
182 session_id TEXT NOT NULL REFERENCES sessions(id),
183 target TEXT NOT NULL,
184 hit_count INTEGER NOT NULL,
185 sample_basis TEXT NOT NULL,
186 sample_period INTEGER,
187 window_us REAL,
188 backend TEXT NOT NULL,
189 collected_at TEXT NOT NULL,
190 detail_json TEXT
191);
192CREATE INDEX IF NOT EXISTS idx_insn_hits_target ON insn_hits(session_id, target);
193
194CREATE TABLE IF NOT EXISTS insn_hit_details (
195 id INTEGER PRIMARY KEY,
196 insn_hit_id INTEGER NOT NULL REFERENCES insn_hits(id),
197 ts_us REAL,
198 stack_json TEXT,
199 regs_json TEXT
200);
201CREATE INDEX IF NOT EXISTS idx_insn_hit_details ON insn_hit_details(insn_hit_id);
202";
203
204pub const GPU_DDL: &str = "
207CREATE TABLE IF NOT EXISTS launches (
208 id INTEGER PRIMARY KEY,
209 session_id TEXT NOT NULL REFERENCES sessions(id),
210 kernel_name TEXT NOT NULL,
211 duration_us REAL NOT NULL,
212 grid_x INTEGER, grid_y INTEGER, grid_z INTEGER,
213 block_x INTEGER, block_y INTEGER, block_z INTEGER,
214 stream_id INTEGER,
215 start_us REAL,
216 correlation_id INTEGER,
217 layer_id INTEGER REFERENCES layers(id)
218);
219CREATE INDEX IF NOT EXISTS idx_launches_kernel ON launches(session_id, kernel_name);
220CREATE INDEX IF NOT EXISTS idx_launches_start ON launches(session_id, start_us);
221CREATE INDEX IF NOT EXISTS idx_launches_stream ON launches(session_id, stream_id);
222
223CREATE TABLE IF NOT EXISTS metrics (
224 session_id TEXT NOT NULL REFERENCES sessions(id),
225 kernel_name TEXT NOT NULL,
226 occupancy_pct REAL,
227 compute_throughput_pct REAL,
228 memory_throughput_pct REAL,
229 registers_per_thread INTEGER,
230 shared_mem_static_bytes INTEGER,
231 shared_mem_dynamic_bytes INTEGER,
232 l2_hit_rate_pct REAL,
233 achieved_bandwidth_gb_s REAL,
234 peak_bandwidth_gb_s REAL,
235 boundedness TEXT,
236 layer_id INTEGER REFERENCES layers(id),
237 PRIMARY KEY (session_id, kernel_name)
238);
239
240CREATE TABLE IF NOT EXISTS transfers (
241 id INTEGER PRIMARY KEY,
242 session_id TEXT NOT NULL REFERENCES sessions(id),
243 kind TEXT NOT NULL,
244 bytes INTEGER,
245 duration_us REAL,
246 start_us REAL,
247 stream_id INTEGER,
248 layer_id INTEGER REFERENCES layers(id)
249);
250
251CREATE TABLE IF NOT EXISTS ops (
252 id INTEGER PRIMARY KEY,
253 session_id TEXT NOT NULL REFERENCES sessions(id),
254 name TEXT NOT NULL,
255 module_path TEXT,
256 cpu_time_us REAL,
257 gpu_time_us REAL,
258 input_shapes TEXT,
259 layer_id INTEGER REFERENCES layers(id)
260);
261
262CREATE TABLE IF NOT EXISTS op_kernel_map (
263 session_id TEXT NOT NULL REFERENCES sessions(id),
264 op_id INTEGER NOT NULL REFERENCES ops(id),
265 kernel_name TEXT NOT NULL,
266 PRIMARY KEY (op_id, kernel_name)
267);
268";
269
270pub const CPU_DDL: &str = "
273CREATE TABLE IF NOT EXISTS samples (
274 id INTEGER PRIMARY KEY,
275 session_id TEXT NOT NULL REFERENCES sessions(id),
276 symbol_id INTEGER REFERENCES symbols(id),
277 thread TEXT,
278 start_us REAL,
279 duration_us REAL,
280 cpu_ns INTEGER,
281 weight REAL NOT NULL DEFAULT 1.0,
282 stack_json TEXT,
283 layer_id INTEGER REFERENCES layers(id)
284);
285CREATE INDEX IF NOT EXISTS idx_samples_sym ON samples(session_id, symbol_id);
286CREATE INDEX IF NOT EXISTS idx_samples_start ON samples(session_id, start_us);
287
288CREATE TABLE IF NOT EXISTS counters (
289 id INTEGER PRIMARY KEY,
290 session_id TEXT NOT NULL REFERENCES sessions(id),
291 name TEXT NOT NULL,
292 symbol_id INTEGER REFERENCES symbols(id),
293 value REAL NOT NULL,
294 unit TEXT,
295 layer_id INTEGER REFERENCES layers(id)
296);
297CREATE INDEX IF NOT EXISTS idx_counters_name ON counters(session_id, name);
298";
299
300pub const MANAGED_DDL: &str = "
302CREATE TABLE IF NOT EXISTS gc_events (
303 id INTEGER PRIMARY KEY,
304 session_id TEXT NOT NULL REFERENCES sessions(id),
305 kind TEXT NOT NULL,
306 pause_us REAL,
307 start_us REAL,
308 heap_before_bytes INTEGER,
309 heap_after_bytes INTEGER,
310 reason TEXT,
311 layer_id INTEGER REFERENCES layers(id)
312);
313CREATE INDEX IF NOT EXISTS idx_gc_start ON gc_events(session_id, start_us);
314
315CREATE TABLE IF NOT EXISTS jit_events (
316 id INTEGER PRIMARY KEY,
317 session_id TEXT NOT NULL REFERENCES sessions(id),
318 symbol_id INTEGER REFERENCES symbols(id),
319 compile_us REAL,
320 code_bytes INTEGER,
321 tier TEXT,
322 start_us REAL,
323 layer_id INTEGER REFERENCES layers(id)
324);
325";
326
327pub const PYTHON_DDL: &str = "
329CREATE TABLE IF NOT EXISTS gil_events (
330 id INTEGER PRIMARY KEY,
331 session_id TEXT NOT NULL REFERENCES sessions(id),
332 kind TEXT NOT NULL,
333 thread TEXT,
334 start_us REAL,
335 duration_us REAL,
336 layer_id INTEGER REFERENCES layers(id)
337);
338";
339
340pub const NODE_DDL: &str = "
342CREATE TABLE IF NOT EXISTS event_loop_lags (
343 id INTEGER PRIMARY KEY,
344 session_id TEXT NOT NULL REFERENCES sessions(id),
345 lag_us REAL NOT NULL,
346 start_us REAL,
347 phase TEXT,
348 layer_id INTEGER REFERENCES layers(id)
349);
350";
351
352pub fn apply(conn: &Connection, class: TargetClass) -> Result<()> {
357 conn.execute_batch(CORE_DDL)?;
358 conn.execute_batch(DEBUG_DDL)?;
359 conn.execute_batch(CROSSTRACK_DDL)?;
360 match class {
361 TargetClass::Gpu => conn.execute_batch(GPU_DDL)?,
362 TargetClass::NativeCpu => conn.execute_batch(CPU_DDL)?,
363 TargetClass::ManagedDotnet | TargetClass::Jvm => {
364 conn.execute_batch(CPU_DDL)?;
365 conn.execute_batch(MANAGED_DDL)?;
366 }
367 TargetClass::Python => {
368 conn.execute_batch(CPU_DDL)?;
369 conn.execute_batch(PYTHON_DDL)?;
370 }
371 TargetClass::JsNode => {
372 conn.execute_batch(CPU_DDL)?;
373 conn.execute_batch(NODE_DDL)?;
374 }
375 TargetClass::Ruby | TargetClass::Php => {
379 conn.execute_batch(CPU_DDL)?;
380 }
381 }
382 conn.execute_batch(OWNERSHIP_TRIGGERS)?;
383 match class {
384 TargetClass::Gpu => conn.execute_batch(GPU_OWNERSHIP_TRIGGERS)?,
385 TargetClass::NativeCpu
386 | TargetClass::ManagedDotnet
387 | TargetClass::Jvm
388 | TargetClass::Python
389 | TargetClass::JsNode
390 | TargetClass::Ruby
391 | TargetClass::Php => conn.execute_batch(CPU_OWNERSHIP_TRIGGERS)?,
392 }
393 match class {
394 TargetClass::ManagedDotnet | TargetClass::Jvm => {
395 conn.execute_batch(MANAGED_OWNERSHIP_TRIGGERS)?
396 }
397 TargetClass::Python => conn.execute_batch(PYTHON_OWNERSHIP_TRIGGERS)?,
398 TargetClass::JsNode => conn.execute_batch(NODE_OWNERSHIP_TRIGGERS)?,
399 _ => {}
400 }
401 Ok(())
402}
403
404pub const OWNERSHIP_TRIGGERS: &str = r#"
409CREATE TRIGGER IF NOT EXISTS disassembly_symbol_same_session
410BEFORE INSERT ON disassembly WHEN NEW.symbol_id IS NOT NULL AND NOT EXISTS
411 (SELECT 1 FROM symbols WHERE id=NEW.symbol_id AND session_id=NEW.session_id)
412BEGIN SELECT RAISE(ABORT, 'disassembly symbol belongs to another session'); END;
413CREATE TRIGGER IF NOT EXISTS disassembly_layer_same_session
414BEFORE INSERT ON disassembly WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
415 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
416BEGIN SELECT RAISE(ABORT, 'disassembly layer belongs to another session'); END;
417CREATE TRIGGER IF NOT EXISTS disassembly_symbol_update_same_session
418BEFORE UPDATE OF session_id, symbol_id ON disassembly
419WHEN NEW.symbol_id IS NOT NULL AND NOT EXISTS
420 (SELECT 1 FROM symbols WHERE id=NEW.symbol_id AND session_id=NEW.session_id)
421BEGIN SELECT RAISE(ABORT, 'disassembly symbol belongs to another session'); END;
422CREATE TRIGGER IF NOT EXISTS disassembly_layer_update_same_session
423BEFORE UPDATE OF session_id, layer_id ON disassembly
424WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
425 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
426BEGIN SELECT RAISE(ABORT, 'disassembly layer belongs to another session'); END;
427CREATE TRIGGER IF NOT EXISTS source_symbol_same_session
428BEFORE INSERT ON source_snapshots WHEN NEW.symbol_id IS NOT NULL AND NOT EXISTS
429 (SELECT 1 FROM symbols WHERE id=NEW.symbol_id AND session_id=NEW.session_id)
430BEGIN SELECT RAISE(ABORT, 'source symbol belongs to another session'); END;
431CREATE TRIGGER IF NOT EXISTS source_symbol_update_same_session
432BEFORE UPDATE OF session_id, symbol_id ON source_snapshots
433WHEN NEW.symbol_id IS NOT NULL AND NOT EXISTS
434 (SELECT 1 FROM symbols WHERE id=NEW.symbol_id AND session_id=NEW.session_id)
435BEGIN SELECT RAISE(ABORT, 'source symbol belongs to another session'); END;
436CREATE TRIGGER IF NOT EXISTS alloc_site_symbol_same_session
437BEFORE INSERT ON alloc_sites WHEN NEW.symbol_id IS NOT NULL AND NOT EXISTS
438 (SELECT 1 FROM symbols WHERE id=NEW.symbol_id AND session_id=NEW.session_id)
439BEGIN SELECT RAISE(ABORT, 'allocation symbol belongs to another session'); END;
440CREATE TRIGGER IF NOT EXISTS alloc_site_symbol_update_same_session
441BEFORE UPDATE OF session_id, symbol_id ON alloc_sites
442WHEN NEW.symbol_id IS NOT NULL AND NOT EXISTS
443 (SELECT 1 FROM symbols WHERE id=NEW.symbol_id AND session_id=NEW.session_id)
444BEGIN SELECT RAISE(ABORT, 'allocation symbol belongs to another session'); END;
445CREATE TRIGGER IF NOT EXISTS alloc_site_layer_same_session
446BEFORE INSERT ON alloc_sites WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
447 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
448BEGIN SELECT RAISE(ABORT, 'allocation layer belongs to another session'); END;
449CREATE TRIGGER IF NOT EXISTS alloc_site_layer_update_same_session
450BEFORE UPDATE OF session_id, layer_id ON alloc_sites
451WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
452 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
453BEGIN SELECT RAISE(ABORT, 'allocation layer belongs to another session'); END;
454CREATE TRIGGER IF NOT EXISTS watch_hit_same_session
455BEFORE INSERT ON watch_evals WHEN NEW.hit_id IS NOT NULL AND NOT EXISTS
456 (SELECT 1 FROM breakpoint_hits WHERE id=NEW.hit_id AND session_id=NEW.session_id)
457BEGIN SELECT RAISE(ABORT, 'watch hit belongs to another session'); END;
458CREATE TRIGGER IF NOT EXISTS watch_hit_update_same_session
459BEFORE UPDATE OF session_id, hit_id ON watch_evals
460WHEN NEW.hit_id IS NOT NULL AND NOT EXISTS
461 (SELECT 1 FROM breakpoint_hits WHERE id=NEW.hit_id AND session_id=NEW.session_id)
462BEGIN SELECT RAISE(ABORT, 'watch hit belongs to another session'); END;
463CREATE TRIGGER IF NOT EXISTS regions_layer_same_session
464BEFORE INSERT ON regions WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
465 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
466BEGIN SELECT RAISE(ABORT, 'region layer belongs to another session'); END;
467CREATE TRIGGER IF NOT EXISTS regions_layer_update_same_session
468BEFORE UPDATE OF session_id, layer_id ON regions
469WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
470 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
471BEGIN SELECT RAISE(ABORT, 'region layer belongs to another session'); END;
472CREATE TRIGGER IF NOT EXISTS allocations_layer_same_session
473BEFORE INSERT ON allocations WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
474 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
475BEGIN SELECT RAISE(ABORT, 'allocation layer belongs to another session'); END;
476CREATE TRIGGER IF NOT EXISTS allocations_layer_update_same_session
477BEFORE UPDATE OF session_id, layer_id ON allocations
478WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
479 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
480BEGIN SELECT RAISE(ABORT, 'allocation layer belongs to another session'); END;
481"#;
482
483pub const GPU_OWNERSHIP_TRIGGERS: &str = r#"
484CREATE TRIGGER IF NOT EXISTS op_map_same_session
485BEFORE INSERT ON op_kernel_map WHEN NOT EXISTS
486 (SELECT 1 FROM ops WHERE id=NEW.op_id AND session_id=NEW.session_id)
487BEGIN SELECT RAISE(ABORT, 'operator link belongs to another session'); END;
488CREATE TRIGGER IF NOT EXISTS op_map_update_same_session
489BEFORE UPDATE OF session_id, op_id ON op_kernel_map WHEN NOT EXISTS
490 (SELECT 1 FROM ops WHERE id=NEW.op_id AND session_id=NEW.session_id)
491BEGIN SELECT RAISE(ABORT, 'operator link belongs to another session'); END;
492CREATE TRIGGER IF NOT EXISTS launches_layer_same_session
493BEFORE INSERT ON launches WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
494 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
495BEGIN SELECT RAISE(ABORT, 'launch layer belongs to another session'); END;
496CREATE TRIGGER IF NOT EXISTS launches_layer_update_same_session
497BEFORE UPDATE OF session_id, layer_id ON launches
498WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
499 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
500BEGIN SELECT RAISE(ABORT, 'launch layer belongs to another session'); END;
501CREATE TRIGGER IF NOT EXISTS metrics_layer_same_session
502BEFORE INSERT ON metrics WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
503 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
504BEGIN SELECT RAISE(ABORT, 'metric layer belongs to another session'); END;
505CREATE TRIGGER IF NOT EXISTS metrics_layer_update_same_session
506BEFORE UPDATE OF session_id, layer_id ON metrics
507WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
508 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
509BEGIN SELECT RAISE(ABORT, 'metric layer belongs to another session'); END;
510CREATE TRIGGER IF NOT EXISTS transfers_layer_same_session
511BEFORE INSERT ON transfers WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
512 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
513BEGIN SELECT RAISE(ABORT, 'transfer layer belongs to another session'); END;
514CREATE TRIGGER IF NOT EXISTS transfers_layer_update_same_session
515BEFORE UPDATE OF session_id, layer_id ON transfers
516WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
517 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
518BEGIN SELECT RAISE(ABORT, 'transfer layer belongs to another session'); END;
519CREATE TRIGGER IF NOT EXISTS ops_layer_same_session
520BEFORE INSERT ON ops WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
521 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
522BEGIN SELECT RAISE(ABORT, 'operator layer belongs to another session'); END;
523CREATE TRIGGER IF NOT EXISTS ops_layer_update_same_session
524BEFORE UPDATE OF session_id, layer_id ON ops
525WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
526 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
527BEGIN SELECT RAISE(ABORT, 'operator layer belongs to another session'); END;
528"#;
529
530pub const CPU_OWNERSHIP_TRIGGERS: &str = r#"
531CREATE TRIGGER IF NOT EXISTS samples_layer_same_session
532BEFORE INSERT ON samples WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
533 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
534BEGIN SELECT RAISE(ABORT, 'sample layer belongs to another session'); END;
535CREATE TRIGGER IF NOT EXISTS samples_symbol_same_session
536BEFORE INSERT ON samples WHEN NEW.symbol_id IS NOT NULL AND NOT EXISTS
537 (SELECT 1 FROM symbols WHERE id=NEW.symbol_id AND session_id=NEW.session_id)
538BEGIN SELECT RAISE(ABORT, 'sample symbol belongs to another session'); END;
539CREATE TRIGGER IF NOT EXISTS samples_layer_update_same_session
540BEFORE UPDATE OF session_id, layer_id ON samples
541WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
542 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
543BEGIN SELECT RAISE(ABORT, 'sample layer belongs to another session'); END;
544CREATE TRIGGER IF NOT EXISTS samples_symbol_update_same_session
545BEFORE UPDATE OF session_id, symbol_id ON samples
546WHEN NEW.symbol_id IS NOT NULL AND NOT EXISTS
547 (SELECT 1 FROM symbols WHERE id=NEW.symbol_id AND session_id=NEW.session_id)
548BEGIN SELECT RAISE(ABORT, 'sample symbol belongs to another session'); END;
549CREATE TRIGGER IF NOT EXISTS counters_layer_same_session
550BEFORE INSERT ON counters WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
551 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
552BEGIN SELECT RAISE(ABORT, 'counter layer belongs to another session'); END;
553CREATE TRIGGER IF NOT EXISTS counters_symbol_same_session
554BEFORE INSERT ON counters WHEN NEW.symbol_id IS NOT NULL AND NOT EXISTS
555 (SELECT 1 FROM symbols WHERE id=NEW.symbol_id AND session_id=NEW.session_id)
556BEGIN SELECT RAISE(ABORT, 'counter symbol belongs to another session'); END;
557CREATE TRIGGER IF NOT EXISTS counters_layer_update_same_session
558BEFORE UPDATE OF session_id, layer_id ON counters
559WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
560 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
561BEGIN SELECT RAISE(ABORT, 'counter layer belongs to another session'); END;
562CREATE TRIGGER IF NOT EXISTS counters_symbol_update_same_session
563BEFORE UPDATE OF session_id, symbol_id ON counters
564WHEN NEW.symbol_id IS NOT NULL AND NOT EXISTS
565 (SELECT 1 FROM symbols WHERE id=NEW.symbol_id AND session_id=NEW.session_id)
566BEGIN SELECT RAISE(ABORT, 'counter symbol belongs to another session'); END;
567"#;
568
569pub const MANAGED_OWNERSHIP_TRIGGERS: &str = r#"
570CREATE TRIGGER IF NOT EXISTS gc_events_layer_same_session
571BEFORE INSERT ON gc_events WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
572 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
573BEGIN SELECT RAISE(ABORT, 'GC layer belongs to another session'); END;
574CREATE TRIGGER IF NOT EXISTS gc_events_layer_update_same_session
575BEFORE UPDATE OF session_id, layer_id ON gc_events
576WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
577 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
578BEGIN SELECT RAISE(ABORT, 'GC layer belongs to another session'); END;
579CREATE TRIGGER IF NOT EXISTS jit_events_symbol_same_session
580BEFORE INSERT ON jit_events WHEN NEW.symbol_id IS NOT NULL AND NOT EXISTS
581 (SELECT 1 FROM symbols WHERE id=NEW.symbol_id AND session_id=NEW.session_id)
582BEGIN SELECT RAISE(ABORT, 'JIT symbol belongs to another session'); END;
583CREATE TRIGGER IF NOT EXISTS jit_events_layer_same_session
584BEFORE INSERT ON jit_events WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
585 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
586BEGIN SELECT RAISE(ABORT, 'JIT layer belongs to another session'); END;
587CREATE TRIGGER IF NOT EXISTS jit_events_symbol_update_same_session
588BEFORE UPDATE OF session_id, symbol_id ON jit_events
589WHEN NEW.symbol_id IS NOT NULL AND NOT EXISTS
590 (SELECT 1 FROM symbols WHERE id=NEW.symbol_id AND session_id=NEW.session_id)
591BEGIN SELECT RAISE(ABORT, 'JIT symbol belongs to another session'); END;
592CREATE TRIGGER IF NOT EXISTS jit_events_layer_update_same_session
593BEFORE UPDATE OF session_id, layer_id ON jit_events
594WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
595 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
596BEGIN SELECT RAISE(ABORT, 'JIT layer belongs to another session'); END;
597"#;
598
599pub const PYTHON_OWNERSHIP_TRIGGERS: &str = r#"
600CREATE TRIGGER IF NOT EXISTS gil_events_layer_same_session
601BEFORE INSERT ON gil_events WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
602 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
603BEGIN SELECT RAISE(ABORT, 'GIL layer belongs to another session'); END;
604CREATE TRIGGER IF NOT EXISTS gil_events_layer_update_same_session
605BEFORE UPDATE OF session_id, layer_id ON gil_events
606WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
607 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
608BEGIN SELECT RAISE(ABORT, 'GIL layer belongs to another session'); END;
609"#;
610
611pub const NODE_OWNERSHIP_TRIGGERS: &str = r#"
612CREATE TRIGGER IF NOT EXISTS event_loop_lags_layer_same_session
613BEFORE INSERT ON event_loop_lags WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
614 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
615BEGIN SELECT RAISE(ABORT, 'event-loop layer belongs to another session'); END;
616CREATE TRIGGER IF NOT EXISTS event_loop_lags_layer_update_same_session
617BEFORE UPDATE OF session_id, layer_id ON event_loop_lags
618WHEN NEW.layer_id IS NOT NULL AND NOT EXISTS
619 (SELECT 1 FROM layers WHERE id=NEW.layer_id AND session_id=NEW.session_id)
620BEGIN SELECT RAISE(ABORT, 'event-loop layer belongs to another session'); END;
621"#;
622
623#[cfg(test)]
624mod tests {
625 use super::*;
626
627 fn in_mem() -> Connection {
628 Connection::open_in_memory().unwrap()
629 }
630
631 #[test]
632 fn applies_cleanly_per_class() {
633 for class in [
634 TargetClass::Gpu,
635 TargetClass::NativeCpu,
636 TargetClass::ManagedDotnet,
637 TargetClass::Jvm,
638 TargetClass::Python,
639 TargetClass::JsNode,
640 TargetClass::Ruby,
641 TargetClass::Php,
642 ] {
643 let c = in_mem();
644 apply(&c, class).unwrap_or_else(|e| panic!("apply({class:?}): {e}"));
645 }
646 }
647
648 #[test]
649 fn apply_is_idempotent() {
650 let c = in_mem();
651 apply(&c, TargetClass::Gpu).unwrap();
652 apply(&c, TargetClass::Gpu).unwrap();
653 apply(&c, TargetClass::Gpu).unwrap();
654 }
655
656 #[test]
657 fn core_tables_exist_after_apply() {
658 let c = in_mem();
659 apply(&c, TargetClass::NativeCpu).unwrap();
660 for t in [
661 "sessions",
662 "layers",
663 "symbols",
664 "meta",
665 "failures",
666 "regions",
667 "allocations",
668 "commands",
669 "breakpoint_hits",
670 "watch_evals",
671 "disassembly",
672 "source_snapshots",
673 "alloc_sites",
674 "insn_hits",
675 "insn_hit_details",
676 "samples",
677 "counters",
678 ] {
679 let exists: i64 = c
680 .query_row(
681 "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?1",
682 [t],
683 |r| r.get(0),
684 )
685 .unwrap();
686 assert_eq!(exists, 1, "missing table: {t}");
687 }
688 }
689
690 #[test]
691 fn gpu_class_has_launches_but_not_samples() {
692 let c = in_mem();
693 apply(&c, TargetClass::Gpu).unwrap();
694 let launches: i64 = c
695 .query_row(
696 "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='launches'",
697 [],
698 |r| r.get(0),
699 )
700 .unwrap();
701 assert_eq!(launches, 1);
702 let samples: i64 = c
703 .query_row(
704 "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='samples'",
705 [],
706 |r| r.get(0),
707 )
708 .unwrap();
709 assert_eq!(samples, 0, "samples table should not exist for GPU class");
710 }
711
712 #[test]
713 fn allocation_layer_links_must_stay_in_the_same_session() {
714 let c = in_mem();
715 apply(&c, TargetClass::NativeCpu).unwrap();
716 c.execute("INSERT INTO sessions (id, kind, target, target_class, started_at, label) VALUES ('a', 'debug', 't', 'native-cpu', 'now', 'a')", []).unwrap();
717 c.execute("INSERT INTO sessions (id, kind, target, target_class, started_at, label) VALUES ('b', 'debug', 't', 'native-cpu', 'now', 'b')", []).unwrap();
718 c.execute(
719 "INSERT INTO layers (session_id, source) VALUES ('b', 'other')",
720 [],
721 )
722 .unwrap();
723 let layer_id = c.last_insert_rowid();
724 assert!(c.execute(
725 "INSERT INTO alloc_sites (session_id, bytes_total, layer_id, collected_at) VALUES ('a', 1, ?1, 'now')",
726 [layer_id],
727 ).is_err());
728 }
729}