Impact Analysis is a command-line tool that extracts SAP BusinessObjects universe and Web Intelligence document metadata into a relational database. Once the data is there, you query it with whatever you already have — SQL Server Management Studio, a Webi report, your favourite IDE, or an AI assistant pointed at the same database via dbquerymcp.
The first run loads everything; subsequent runs are incremental — only changed universes and documents are re-extracted. A water-mark timestamp and a per-cuid status table track what’s been seen and what failed.
Edit config/jdbc.json. SQLite is the default — nothing to install, the file sits next to the launcher:
{
"driver": "org.sqlite.JDBC",
"url": "jdbc:sqlite:impactanalysis.db",
"username": "",
"password": ""
}
For SQL Server:
{
"driver": "com.microsoft.sqlserver.jdbc.SQLServerDriver",
"url": "jdbc:sqlserver://HOST:1433;databaseName=DBNAME;encrypt=true;trustServerCertificate=true",
"username": "USERNAME",
"password": "PASSWORD"
}
For Oracle:
{
"driver": "oracle.jdbc.OracleDriver",
"url": "jdbc:oracle:thin:@HOST:1521:SID",
"username": "USERNAME",
"password": "PASSWORD"
}
All three drivers are bundled — no separate downloads required.
Edit config/systems.json with the BIP REST endpoint plus BO Client Tools credentials (the tool drives webimaster for documents over REST and unxdoc for universes through the local Semantic Layer SDK):
{
"lastSystem": "prod",
"systems": [
{ "system": "prod", "username": "Administrator", "password": "..." }
]
}
Edit config/folders.txt. One folder path per line; comments start with #:
# CMS folders to scan for Webi documents and universes.
/Reporting/Sales
/Reporting/Operations
/Universes
Sub-folders are walked recursively. Anything outside the listed folders is ignored.
impactanalysis-cli initdb
This applies impactanalysis’s own schema, plus the bundled webimaster and unxdoc schemas — one self-contained database, no sibling tools required.
| Command | What it does |
|---|---|
initdb |
One-time DB setup. Applies all three schemas (impactanalysis + webimaster + unxdoc) in one transaction. |
refresh |
Incremental extraction. Walks config/folders.txt, picks up new and changed cuids since the last run’s water mark, prunes cuids deleted from BO. |
retry-failures |
Re-runs extraction only for cuids that were marked failed in a previous run. Use after fixing the underlying cause (broken document, missing universe, etc.). |
status |
Prints the water mark, the last run summary, and current failure counts. |
A typical operational loop is refresh on a schedule (nightly, hourly), status to check health, retry-failures after fixing anything broken.
Three logical layers, one schema:
| Source | Tables |
|---|---|
| unxdoc (universes) | universe, universe_object, universe_table, universe_join, universe_context, universe_parameter, universe_lov, universe_navigation_path, universe_incompatibility |
| webimaster (Webi documents) | document, document_data_provider, document_result_object, document_variable, document_link, document_link_member, document_layout_site, document_reference |
| impactanalysis (run tracking) | impactanalysis_run (water mark), impactanalysis_cuid (per-cuid status) |
The two join keys that bridge documents to universes:
| webimaster column | unxdoc column |
|---|---|
document_data_provider.data_source_cuid |
universe.universe_cuid |
document_result_object.data_source_object_id |
universe_object.object_id (within the same universe) |
These are stable cuids assigned by BO, not surrogate keys generated at extraction time — they survive renames and folder moves.
Given a universe cuid and an object name, find the result objects that pull from it plus the variables and layout sites that reference those result objects.
WITH target_obj AS (
SELECT universe_cuid, object_id
FROM universe_object
WHERE universe_cuid = :univ
AND name = :object_name -- or filter by select_expr LIKE '%column%'
),
hit_ro AS (
SELECT dp.document_cuid, ro.result_object_id, ro.name AS result_object_name
FROM document_data_provider dp
JOIN document_result_object ro ON ro.document_cuid = dp.document_cuid AND ro.dp_id = dp.dp_id
JOIN target_obj t ON t.universe_cuid = dp.data_source_cuid
AND t.object_id = ro.data_source_object_id
)
SELECT d.path, d.name AS document, hit_ro.result_object_name,
ref.source_kind, ref.source_id, ref.target_name AS referenced_as
FROM hit_ro
JOIN document d ON d.document_cuid = hit_ro.document_cuid
LEFT JOIN document_reference ref ON ref.document_cuid = hit_ro.document_cuid
AND ref.target_kind = 'result_object'
AND ref.target_id = hit_ro.result_object_id
ORDER BY d.path, d.name;
source_kind = 'variable' rows tell you a variable formula references the object; source_kind = 'site' rows mean a report element (block, axis, sort, filter) references it. A NULL source_kind row means the object is selected in a query but never referenced downstream — usually safe to remove.
SELECT object_id, name, select_expr, where_expr
FROM universe_object
WHERE universe_cuid = :univ
AND (select_expr LIKE :pattern OR where_expr LIKE :pattern);
pattern is something like '%SCHEMA.TABLE.COLUMN%'. For derived tables you may need to walk universe_table.expression as well.
document_reference is the explicit edge table. Variables → result objects is one hop; variables → variables → result objects is N hops. Use a recursive CTE:
WITH RECURSIVE chain(document_cuid, var_id, hop) AS (
SELECT document_cuid, source_id, 1
FROM document_reference
WHERE source_kind = 'variable' AND target_kind = 'result_object' AND target_id = :ro_id
UNION ALL
SELECT r.document_cuid, r.source_id, c.hop + 1
FROM document_reference r
JOIN chain c
ON r.document_cuid = c.document_cuid
AND r.target_kind = 'variable'
AND r.target_id = c.var_id
WHERE r.source_kind = 'variable' AND c.hop < 10
)
SELECT DISTINCT v.name, c.hop
FROM chain c
JOIN document_variable v ON v.document_cuid = c.document_cuid AND v.variable_id = c.var_id
ORDER BY c.hop, v.name;
impactanalysis_run.water_mark is the timestamp the most recent refresh extracted up to. To know what to trust:
impactanalysis_cuid.status = 'ok' were extracted successfully on the last run that touched them.status = 'failed' were attempted but did not land in the DB — treat them as stale until retry-failures succeeds.Always check impactanalysis-cli status (or query impactanalysis_run) before relying on the data for a critical decision.
@Select(), @DerivedTable(), @Variable() are stored verbatim — not expanded. A query against select_expr may need to traverse them.retry-failures is run.