Memory snapshot database SQL
Use this when working with the database the tool exports from a .snap (the export command), as
opposed to running the export/report pipeline itself (use memory-snapshot-report for that).
Canonical schema reference: docs/database-schema.md. It
lists every table, column, view, macro, join key, and the version policy. Read it before writing
non-trivial queries — this skill is the workflow; that doc is the source of truth.
Before you query: check the schema version
The database stamps a major.minor version in schema_meta:
SELECT schema_version_major, schema_version_minor, msdt_version FROM schema_meta;
- No
schema_meta table → a pre-versioning export (treated as 0.0): re-export the .snap.
- major <
DatabaseSchemaInfo.SchemaMajor → table/column structure changed; re-export required
(MemorySnapshotDataTools export "<snap>" "<db>").
- same major, minor <
DatabaseSchemaInfo.SchemaMinor → only views/indexes changed; upgrade in
place with MemorySnapshotDataTools upgrade "<db>" (no re-export).
- In code, classify with
DatabaseSchemaInfo.Evaluate(major, minor) → SchemaAction
(None/UpgradeInPlace/ReExport/ToolOutdated); the CLI SchemaGate already does this before
report/summary/validate, and DatabaseMaintenance.{Inspect,UpgradeInPlace} are the entry
points (see Core/Models/DatabaseSchemaInfo.cs).
Querying: the things that bite
These are spelled out in the schema doc, but the high-frequency traps:
- Two region tables, no FK between them.
memory_regions = Unity allocator buckets
(native_allocations.memory_region_index points here). system_memory_regions = OS/VM regions
(the RAM truth). Bridge an allocation to its OS region by address range, not a key.
- Prefer the views/macros over rebuilding joins:
v_allocation_enriched (allocation + Unity
region + OS region + root + object), v_system_region_summary (committed/resident/Unity-tracked
per OS region), v_region_owner_breakdown, v_connection_edges (reference graph with both
endpoints typed — filter it, don't SELECT *), v_assetbundle_utilization (per-bundle: does it
reference other loaded assets, and how much). DuckDB also has region_allocations(name) and
region_page_density(name) (SQLite has the views only).
- Connections: don't hand-count "own" edges with magic numbers. A native object's outbound edges
include its self-reference and its managed wrappers (
native_gc_handle_bridge/native_connection
to managed). For "references to other loaded objects," count native_object→native_object
native_connection edges where to_index <> from_index — exactly what v_assetbundle_utilization
does.
native_object_address ≠ native_allocations.address — bridge objects and allocations
through a shared root_reference_id → native_roots.root_id, never by address.
- Don't divide by
memory_regions.address_size (0 for ALLOC_DEFAULT et al.).
system_memory_regions.type is uniformly 0 on iOS — group by name.
- Resident data and
page_size require snap_format_version ≥ 17 (else NULL).
region_page_density is for small-allocation zones (NANO/TINY/SMALL); avg_fill_pct > 100%
means the region holds page-spanning allocations and the metric doesn't apply.
Writing query code safely
This repo's first-class rule is SQL safety — see CLAUDE.md and
docs/sql-safety.md:
- Parameterize every value. DuckDB: positional
?. SQLite: named $name.
- Identifiers can't be parameters — validate against the catalog (
information_schema.columns
for DuckDB, pragma_table_info($t) for SQLite); see the HasColumn helpers.
- Open read-only for analysis (
ACCESS_MODE=READ_ONLY / Mode=ReadOnly).
- The report
ExecuteQuery(string) sink takes only internally-constructed SQL (constants in
ReportSql); never pass it external input.
Ad-hoc CLI querying (no SQL command exists in the tool): use the duckdb CLI on a .duckdb file
(open with -readonly; if a lock error mentions DataGrip, ask the user to close that connection).
Upkeep: when you change the schema
A schema change is not done until all of these are consistent:
- Both backends — mirror the change in
DuckDbExportDestination.cs and
SqliteWriter.cs (tables, indexes, and the
CreateViewsScript). Remember: DuckDB has ASOF joins and table macros; SQLite does not (use a
correlated subquery; macros are DuckDB-only). Keep index/view DDL re-runnable (CREATE INDEX IF NOT EXISTS, DuckDB CREATE OR REPLACE VIEW / SQLite drop-then-create) so UpgradeSchema works.
- The doc — update
docs/database-schema.md: table/column
tables, view/macro list, join keys, and the version table. New tables, views, columns, and
identifiers must appear here.
- The version — in
Core/Models/DatabaseSchemaInfo.cs:
- View/index change only (add/change a view or index): bump
SchemaMinor. Existing
databases can be upgraded in place (MemorySnapshotDataTools upgrade), so no re-export.
- Table/column change (add/rename/remove a table or column, or change a column's
meaning/units): bump
SchemaMajor and reset SchemaMinor to 0. Old databases require a
re-export. Make sure both writers' table DDL and schema_meta insert stay in sync.
- Add a row to the doc's version table either way.
- Readers —
snapshot_info is read with SELECT * by column name
(SummaryReportRunner.ReadSnapshotInfo), so new columns are safe; if you add a column some
reader needs, wire it there.
- Tests — extend
DatabaseSchemaInfoTests and the export round-trip tests so the new
schema/view/version is asserted.
See also
memory-snapshot-report skill — export a .snap to a database and generate reports.
docs/database-schema.md — canonical schema.
1---2name: memory-db-sql3description: Write and manage SQL queries against an exported Unity memory-snapshot database (DuckDB/SQLite), and keep the schema, views, version, and docs consistent. Use when writing/editing queries over native_objects, native_allocations, native_roots, memory_regions, system_memory_regions, etc.; investigating memory regions; adding or changing tables/views/macros; or checking database version compatibility.4---56# Memory snapshot database SQL78Use this when working with the database the tool exports from a `.snap` (the `export` command), as9opposed to running the export/report pipeline itself (use `memory-snapshot-report` for that).1011**Canonical schema reference:** [`docs/database-schema.md`](../../../docs/database-schema.md). It12lists every table, column, view, macro, join key, and the version policy. Read it before writing13non-trivial queries — this skill is the workflow; that doc is the source of truth.1415## Before you query: check the schema version1617The database stamps a **major.minor** version in `schema_meta`:1819```sql20SELECT schema_version_major, schema_version_minor, msdt_version FROM schema_meta;21```2223- **No `schema_meta` table** → a pre-versioning export (treated as 0.0): re-export the `.snap`.24- **major < `DatabaseSchemaInfo.SchemaMajor`** → table/column structure changed; **re-export required**25 (`MemorySnapshotDataTools export "<snap>" "<db>"`).26- **same major, minor < `DatabaseSchemaInfo.SchemaMinor`** → only views/indexes changed; **upgrade in27 place** with `MemorySnapshotDataTools upgrade "<db>"` (no re-export).28- In code, classify with `DatabaseSchemaInfo.Evaluate(major, minor)` → `SchemaAction`29 (`None`/`UpgradeInPlace`/`ReExport`/`ToolOutdated`); the CLI `SchemaGate` already does this before30 `report`/`summary`/`validate`, and `DatabaseMaintenance.{Inspect,UpgradeInPlace}` are the entry31 points (see [`Core/Models/DatabaseSchemaInfo.cs`](../../../Core/Models/DatabaseSchemaInfo.cs)).3233## Querying: the things that bite3435These are spelled out in the schema doc, but the high-frequency traps:3637- **Two region tables, no FK between them.** `memory_regions` = Unity allocator buckets38 (`native_allocations.memory_region_index` points here). `system_memory_regions` = OS/VM regions39 (the RAM truth). Bridge an allocation to its OS region **by address range**, not a key.40- **Prefer the views/macros** over rebuilding joins: `v_allocation_enriched` (allocation + Unity41 region + OS region + root + object), `v_system_region_summary` (committed/resident/Unity-tracked42 per OS region), `v_region_owner_breakdown`, `v_connection_edges` (reference graph with both43 endpoints typed — filter it, don't `SELECT *`), `v_assetbundle_utilization` (per-bundle: does it44 reference other loaded assets, and how much). DuckDB also has `region_allocations(name)` and45 `region_page_density(name)` (SQLite has the views only).46- **Connections: don't hand-count "own" edges with magic numbers.** A native object's outbound edges47 include its self-reference and its managed wrappers (`native_gc_handle_bridge`/`native_connection`48 to managed). For "references to *other* loaded objects," count `native_object→native_object`49 `native_connection` edges where `to_index <> from_index` — exactly what `v_assetbundle_utilization`50 does.51- **`native_object_address` ≠ `native_allocations.address`** — bridge objects and allocations52 through a shared `root_reference_id` → `native_roots.root_id`, never by address.53- **Don't divide by `memory_regions.address_size`** (0 for `ALLOC_DEFAULT` et al.).54- **`system_memory_regions.type` is uniformly 0 on iOS** — group by `name`.55- **Resident data and `page_size` require `snap_format_version` ≥ 17** (else NULL).56- `region_page_density` is for **small-allocation zones** (NANO/TINY/SMALL); `avg_fill_pct` > 100%57 means the region holds page-spanning allocations and the metric doesn't apply.5859## Writing query code safely6061This repo's first-class rule is SQL safety — see [`CLAUDE.md`](../../../CLAUDE.md) and62[`docs/sql-safety.md`](../../../docs/sql-safety.md):6364- **Parameterize every value.** DuckDB: positional `?`. SQLite: named `$name`.65- **Identifiers can't be parameters** — validate against the catalog (`information_schema.columns`66 for DuckDB, `pragma_table_info($t)` for SQLite); see the `HasColumn` helpers.67- **Open read-only** for analysis (`ACCESS_MODE=READ_ONLY` / `Mode=ReadOnly`).68- The report `ExecuteQuery(string)` sink takes only internally-constructed SQL (constants in69 `ReportSql`); never pass it external input.7071Ad-hoc CLI querying (no SQL command exists in the tool): use the `duckdb` CLI on a `.duckdb` file72(open with `-readonly`; if a lock error mentions DataGrip, ask the user to close that connection).7374## Upkeep: when you change the schema7576A schema change is not done until **all** of these are consistent:77781. **Both backends** — mirror the change in79 [`DuckDbExportDestination.cs`](../../../Core/ExportDestination/DuckDbExportDestination.cs) **and**80 [`SqliteWriter.cs`](../../../Core/ExportDestination/SqliteWriter.cs) (tables, indexes, and the81 `CreateViewsScript`). Remember: DuckDB has `ASOF` joins and table macros; SQLite does not (use a82 correlated subquery; macros are DuckDB-only). Keep index/view DDL **re-runnable** (`CREATE INDEX83 IF NOT EXISTS`, DuckDB `CREATE OR REPLACE VIEW` / SQLite drop-then-create) so `UpgradeSchema` works.842. **The doc** — update [`docs/database-schema.md`](../../../docs/database-schema.md): table/column85 tables, view/macro list, join keys, and the version table. New tables, views, columns, and86 identifiers must appear here.873. **The version** — in88 [`Core/Models/DatabaseSchemaInfo.cs`](../../../Core/Models/DatabaseSchemaInfo.cs):89 - **View/index change only** (add/change a view or index): bump **`SchemaMinor`**. Existing90 databases can be upgraded in place (`MemorySnapshotDataTools upgrade`), so no re-export.91 - **Table/column change** (add/rename/remove a table or column, or change a column's92 meaning/units): bump **`SchemaMajor`** and reset `SchemaMinor` to 0. Old databases require a93 re-export. Make sure both writers' table DDL and `schema_meta` insert stay in sync.94 - Add a row to the doc's version table either way.954. **Readers** — `snapshot_info` is read with `SELECT *` by column name96 (`SummaryReportRunner.ReadSnapshotInfo`), so new columns are safe; if you add a column some97 reader needs, wire it there.985. **Tests** — extend `DatabaseSchemaInfoTests` and the export round-trip tests so the new99 schema/view/version is asserted.100101## See also102103- `memory-snapshot-report` skill — export a `.snap` to a database and generate reports.104- [`docs/database-schema.md`](../../../docs/database-schema.md) — canonical schema.