|
| 1 | +# Design: Design-time → Runtime Name Resolution for Artifact Inspectors |
| 2 | + |
| 3 | +**Date:** 2026-07-15 |
| 4 | +**Component:** VSCode extension (`vscode-extension/`) |
| 5 | +**Status:** Approved |
| 6 | + |
| 7 | +## Problem |
| 8 | + |
| 9 | +When a user opens an HDI design-time file in a hana-cli custom editor (e.g. |
| 10 | +`cds.outbox.Messages.hdbtable`), the artifact inspector injects the file's |
| 11 | +**design-time** name into the inspect input field. HANA does not know that |
| 12 | +name, so the lookup fails with "Invalid Input Table." |
| 13 | + |
| 14 | +The design-time name and the runtime object name differ: |
| 15 | + |
| 16 | +| Design-time (filename) | Runtime (deployed object) | |
| 17 | +| ------------------------------ | ------------------------- | |
| 18 | +| `cds.outbox.Messages.hdbtable` | `cds_outbox_Messages` | |
| 19 | +| `star.wars.X.hdbview` | `star_wars_X` | |
| 20 | + |
| 21 | +Today, [`artifactInspector.ts`](../../../vscode-extension/src/editors/artifactInspector.ts) |
| 22 | +(lines 78–80) strips only the file extension: |
| 23 | + |
| 24 | +```ts |
| 25 | +const filename = path.basename(document.uri.fsPath) |
| 26 | +const dotIndex = filename.lastIndexOf('.') |
| 27 | +const name = dotIndex > 0 ? filename.substring(0, dotIndex) : filename |
| 28 | +``` |
| 29 | + |
| 30 | +For `cds.outbox.Messages.hdbtable` this yields `cds.outbox.Messages`, which is |
| 31 | +then injected verbatim via a route query param |
| 32 | +(`/inspect-table?table=cds.outbox.Messages`). |
| 33 | + |
| 34 | +### Why the lookup fails |
| 35 | + |
| 36 | +1. **Dot → underscore transformation.** CAP/HDI replaces `.` with `_` in the |
| 37 | + deployed object name. The file contains the real name in its DDL, e.g. |
| 38 | + `COLUMN TABLE cds_outbox_Messages (...)`. |
| 39 | +2. **Case sensitivity.** The backend matches names **exactly and |
| 40 | + case-sensitively** — see |
| 41 | + [`dbInspect.js`](../../../utils/dbInspect.js) `getTable`, which queries |
| 42 | + `SYS.TABLES` with `AND TABLE_NAME = ?` (not `LIKE`, not uppercased). HDI |
| 43 | + deploys objects with quoted, mixed-case identifiers |
| 44 | + (`cds_outbox_Messages`), so any casing change breaks the match. |
| 45 | + |
| 46 | +## Goal |
| 47 | + |
| 48 | +For every connected artifact inspector editor, inject the correct **runtime** |
| 49 | +object name — preserving exact case — so the inspect lookup succeeds without |
| 50 | +the user editing the field manually. |
| 51 | + |
| 52 | +## Scope |
| 53 | + |
| 54 | +**In scope** — the artifact kinds registered in `ARTIFACT_CONFIGS` |
| 55 | +([`artifactInspector.ts`](../../../vscode-extension/src/editors/artifactInspector.ts) |
| 56 | +lines 14–22): |
| 57 | + |
| 58 | +| Kind | File pattern(s) | Route | |
| 59 | +| --------- | ---------------------------------- | ------------------ | |
| 60 | +| table | `.hdbtable`, `.hdbmigrationtable` | `/inspect-table` | |
| 61 | +| view | `.hdbview` | `/inspect-view` | |
| 62 | +| procedure | `.hdbprocedure` | `/call-procedure` | |
| 63 | +| function | `.hdbfunction` | `/inspect-function`| |
| 64 | +| synonym | `.hdbsynonym` | `/inspect-table` | |
| 65 | +| role | `.hdbrole` | `/inspect-table` | |
| 66 | +| sequence | `.hdbsequence` | `/inspect-table` | |
| 67 | + |
| 68 | +**Out of scope** |
| 69 | + |
| 70 | +- `calcViewEditor.ts` — the `.hdbcalculationview` editor is a separate provider |
| 71 | + with its own XML handling; not touched by this change. |
| 72 | +- Server (`routes/`), CLI, and Vue view changes — the resolution is entirely |
| 73 | + extension-side. |
| 74 | + |
| 75 | +## Approach |
| 76 | + |
| 77 | +A new extension-side resolver module reads the design-time file and extracts |
| 78 | +the runtime name from its DDL/content. This is authoritative: it uses the exact |
| 79 | +name the deployer wrote, preserving case and any namespace prefix. If parsing |
| 80 | +fails, it falls back to a filename-based naming rule. |
| 81 | + |
| 82 | +**Decisions confirmed during brainstorming:** |
| 83 | + |
| 84 | +- **Translation source:** parse file content (not a naming rule alone, not a |
| 85 | + live DB lookup). |
| 86 | +- **Fallback:** naming-rule fallback (strip extension, dots→underscores, apply |
| 87 | + `.hdinamespace` prefix) when parsing fails — always inject a best-effort name. |
| 88 | +- **Namespace:** handled by the parse path automatically (DDL is already fully |
| 89 | + qualified); `.hdinamespace` is only read for the fallback rule. |
| 90 | +- **Location:** extension-side resolver module (TypeScript), unit-testable in |
| 91 | + the extension test suite; no server round-trip. |
| 92 | + |
| 93 | +## Component: `resolveRuntimeName` |
| 94 | + |
| 95 | +New file: `vscode-extension/src/editors/runtimeName.ts` |
| 96 | + |
| 97 | +```ts |
| 98 | +/** |
| 99 | + * Resolve the runtime (deployed) object name for an HDI design-time file. |
| 100 | + * Reads the file content and extracts the name from its DDL/JSON; falls back |
| 101 | + * to a filename-based naming rule if parsing fails. Never throws. |
| 102 | + * |
| 103 | + * @param fsPath absolute path to the design-time file |
| 104 | + * @param kind artifact kind from ArtifactConfig ('table' | 'view' | ...) |
| 105 | + * @returns the runtime object name, case preserved |
| 106 | + */ |
| 107 | +export function resolveRuntimeName(fsPath: string, kind: string): string |
| 108 | +``` |
| 109 | + |
| 110 | +### Per-kind parsers |
| 111 | + |
| 112 | +Matched by the `kind` value already present on each `ArtifactConfig`. All |
| 113 | +parsers operate on the file's text content (read synchronously; these files are |
| 114 | +small). |
| 115 | + |
| 116 | +| Kind | Content type | Extraction rule | |
| 117 | +| --------- | ------------ | ------------------------------------------------------ | |
| 118 | +| table | DDL | first match of `(?:COLUMN\|ROW)?\s*TABLE\s+<ident>` | |
| 119 | +| view | DDL | `VIEW\s+<ident>\s+AS` | |
| 120 | +| function | DDL | `FUNCTION\s+<ident>` | |
| 121 | +| procedure | DDL | `PROCEDURE\s+<ident>` | |
| 122 | +| sequence | DDL or JSON | detect: JSON → top-level name key; else `SEQUENCE\s+<ident>` | |
| 123 | +| synonym | JSON | top-level object key (the synonym's own name) | |
| 124 | +| role | JSON | top-level object key (the role's own name) | |
| 125 | + |
| 126 | +Where `<ident>` is either a bare identifier (`cds_outbox_Messages`) or a |
| 127 | +double-quoted identifier (`"cds_outbox_Messages"`), optionally schema-qualified |
| 128 | +(`schema.name`, `"schema"."name"`). |
| 129 | + |
| 130 | +### Identifier normalization |
| 131 | + |
| 132 | +- Strip surrounding double-quotes but **preserve inner case**. |
| 133 | +- For **DDL** names that are schema-qualified with a dot (`schema.name`, |
| 134 | + `"schema"."name"`), take the **last dot segment** (the object name); the |
| 135 | + schema is supplied separately by the inspector (defaults to |
| 136 | + `**CURRENT_SCHEMA**`). |
| 137 | +- For **JSON** keys (synonym/role/sequence), use the key verbatim after |
| 138 | + quote-stripping. A `::` namespace separator is part of the runtime object |
| 139 | + name and is **kept** (not split), unlike a dot schema qualifier. |
| 140 | +- Do **not** upper/lowercase the result — inject verbatim so it matches the |
| 141 | + quoted mixed-case identifier HDI deployed. |
| 142 | + |
| 143 | +### Fallback rule |
| 144 | + |
| 145 | +Triggered when: file read fails, content does not match the parser, the kind is |
| 146 | +unknown, or the extracted name is empty. |
| 147 | + |
| 148 | +1. Strip the file extension from the basename. |
| 149 | +2. Replace `.` with `_`. |
| 150 | +3. If an ancestor `.hdinamespace` file exists with a non-empty `name` field, |
| 151 | + prepend `<name>::`. |
| 152 | + |
| 153 | +This mirrors HDI's own naming transformation and guarantees the field is always |
| 154 | +populated with a reasonable guess (never worse than today's behavior). |
| 155 | + |
| 156 | +## Integration |
| 157 | + |
| 158 | +In [`artifactInspector.ts`](../../../vscode-extension/src/editors/artifactInspector.ts), |
| 159 | +`resolveCustomEditor` replaces the extension-strip logic (lines 77–80) with: |
| 160 | + |
| 161 | +```ts |
| 162 | +const name = resolveRuntimeName(document.uri.fsPath, this._config.kind) |
| 163 | +``` |
| 164 | + |
| 165 | +The remainder of the flow — building `routeWithName`, the webview content, the |
| 166 | +message handlers — is unchanged. The resolved name flows through the existing |
| 167 | +`queryKey` query param mechanism to the target Vue view. |
| 168 | + |
| 169 | +## Error handling |
| 170 | + |
| 171 | +- `resolveRuntimeName` never throws. All failure modes (I/O error, malformed |
| 172 | + content, unsupported kind) degrade to the fallback rule. |
| 173 | +- No new user-facing error surface; the inspector continues to render, and a |
| 174 | + wrong guess is correctable by the user in the field as today. |
| 175 | + |
| 176 | +## Testing |
| 177 | + |
| 178 | +Unit tests in `vscode-extension/test/suite/` (e.g. `runtimeName.test.ts`), |
| 179 | +using real DDL/JSON fixtures mirroring the samples observed in the |
| 180 | +`cloud-cap-hana-swapi` project: |
| 181 | + |
| 182 | +**Parse-path cases (one per kind):** |
| 183 | + |
| 184 | +- table: `COLUMN TABLE cds_outbox_Messages (...)` → `cds_outbox_Messages` |
| 185 | +- view: `VIEW star_wars_CloneWarsChronologicalOrder AS SELECT ...` → |
| 186 | + `star_wars_CloneWarsChronologicalOrder` |
| 187 | +- function / procedure / sequence: analogous DDL snippets |
| 188 | +- synonym / role: JSON with a single top-level name key |
| 189 | + |
| 190 | +**Normalization cases:** |
| 191 | + |
| 192 | +- quoted identifier: `TABLE "cds_outbox_Messages"` → unquoted, case preserved |
| 193 | +- schema-qualified: `"MYSCHEMA"."cds_outbox_Messages"` → last segment |
| 194 | +- mixed case preserved (no upper/lowercasing) |
| 195 | + |
| 196 | +**Fallback cases:** |
| 197 | + |
| 198 | +- empty file → naming rule |
| 199 | +- unknown kind → naming rule |
| 200 | +- unparseable content → naming rule |
| 201 | +- `.hdinamespace` with non-empty `name` → `<name>::` prefix applied |
| 202 | +- `.hdinamespace` empty / absent → no prefix |
| 203 | + |
| 204 | +## Files changed |
| 205 | + |
| 206 | +| File | Change | |
| 207 | +| ------------------------------------------------- | ------------------------------- | |
| 208 | +| `vscode-extension/src/editors/runtimeName.ts` | new — resolver + parsers | |
| 209 | +| `vscode-extension/src/editors/artifactInspector.ts` | use `resolveRuntimeName` | |
| 210 | +| `vscode-extension/test/suite/runtimeName.test.ts` | new — unit tests | |
| 211 | + |
| 212 | +Per project memory: bump the extension version before repackaging the `.vsix`, |
| 213 | +and package via `npm run package` (not `vsce package --no-dependencies`). |
0 commit comments