From 3457c3a7f101b1c21e38640a72bd829281e83c47 Mon Sep 17 00:00:00 2001 From: Pierre GIRAUD Date: Thu, 9 Oct 2025 08:55:24 +0200 Subject: [PATCH 1/2] ui: Revise find function to accept more wildcards The function is rewritten to accept wildcards not only for the schema name. --- internal/cmd/ui/src/dbobjects.js | 94 +++++++++++++++++---------- internal/cmd/ui/src/test/find.test.js | 68 +++++++++++++++++-- 2 files changed, 121 insertions(+), 41 deletions(-) diff --git a/internal/cmd/ui/src/dbobjects.js b/internal/cmd/ui/src/dbobjects.js index 4cc1b5275..fcaaf5918 100644 --- a/internal/cmd/ui/src/dbobjects.js +++ b/internal/cmd/ui/src/dbobjects.js @@ -306,13 +306,14 @@ function formatDefinition(obj) { * - `SAKILA` for a schema object * - `SAKILA/Tables` for all tables having `SAKILA` as Schema attribute. * - `-/Tables` for all tables, without schema filtering. + * - `-/Tables/-/Indexes` for all indexes, without schema nor table filtering. * - `SAKILA/Packages/CUSTOMERS/Functions/GET_CUSTOMER` to get component of a package. */ -export function find(obj, path) { +export function find(model, path) { // Case empty path -> root object. if (path === "") { - return Object.keys(obj).length != 0 ? obj : undefined; + return Object.keys(model).length != 0 ? model : undefined; } // Remove leading slash if present @@ -320,48 +321,69 @@ export function find(obj, path) { path = path.slice(1); } + // Schemaâ‹…s only if (!path.includes("/")) { - if (path === "-") { - // Case all Schemas - path = "-/Schemas"; - } else { - // Case bare schema name - path = `-/Schemas/${path}`; - } + path = path === "-" ? "-/Schemas" : `-/Schemas/${path}`; } - let [schema, ...segments] = path.split("/"); - - for (const segment of segments) { - if (Array.isArray(obj)) { - obj = obj.filter((o) => o.Name === segment); - // If no objects are found, return undefined - if (obj.length === 0) return undefined; - // Returns the single object if only one found - if (obj.length === 1) { - obj = obj[0]; - } else { - // homonyms, stop loop and let UI present the ambiguity - return obj; - } - } else if (typeof obj === "object" && obj !== null) { - obj = obj[segment]; - if (!Array.isArray(obj)) { - // refuse non array of dbobjects + const parts = path.split("/"); + const [schema, type, ...rest] = parts; + + // Special case for schema name only + if (type === "Schemas") { + return parts[2] + ? model.Schemas?.find((s) => s.Name && s.Name == parts[2]) + : model.Schemas; + } + + function resolve(current, index) { + if (current === null) return undefined; + + // Last segment + if (index == rest.length) { + // Forbid primitives (ie. not object or array) + return typeof current === "object" ? current : undefined; + } + + const segment = rest[index]; + + if (segment === "-") { + const arr = Array.isArray(current) ? current : [current]; + const results = arr + .map((item) => resolve(item, index + 1)) + .flat() + .filter((x) => x !== undefined); + return results.length ? results : undefined; + } + + // Array lookup by Name + if (Array.isArray(current)) { + const matches = current.filter((item) => item.Name === segment); + if (matches.length === 0) return undefined; + if (matches.length > 1 && index < rest.length - 1) { + console.error("Invalid path"); return undefined; } - // Filter by schema if provided - if (schema !== "-") { - obj = obj.filter((s) => s.Schema === schema); - // Then, disable schema filtering for children. - schema = "-"; - } - } else { - return undefined; + return matches.length > 1 + ? matches // homonyms + : resolve(matches[0], index + 1); } + + // Object lookup + if (typeof current === "object") { + return resolve(current[segment], index + 1); + } + + return undefined; } - return obj; + // Starting point: Tables, Packages, etc. + const collection = model[type]; + if (!collection) return undefined; + + const filtered = + schema === "-" ? collection : collection.filter((o) => o.Schema === schema); + return resolve(filtered, 0); } export function expandParents(path) { diff --git a/internal/cmd/ui/src/test/find.test.js b/internal/cmd/ui/src/test/find.test.js index c7c3075c0..0229a1309 100644 --- a/internal/cmd/ui/src/test/find.test.js +++ b/internal/cmd/ui/src/test/find.test.js @@ -19,15 +19,43 @@ const obj = { { Name: "ACTOR_ID", }, + { + Name: "ACTOR_LAST_NAME", + }, + ], + Indexes: [ + { + Name: "IDX_ACTOR_ID", + }, ], }, { Schema: "SAKILA", Name: "USER", + Columns: [ + { + Name: "USER_ID", + }, + ], + Indexes: [ + { + Name: "IDX_USER_ID", + }, + ], }, { Schema: "XTRA", Name: "USER", + Columns: [ + { + Name: "USER_ID", + }, + ], + Indexes: [ + { + Name: "IDX_USER_ID", + }, + ], }, ], @@ -37,9 +65,16 @@ const obj = { Name: "CUSTOMERS", Functions: [ { - Schema: "", Name: "GET_CUSTOMER", }, + { + Name: "GET_CUSTOMER_RENTAL_HISTORY", + Signature: "INT", + }, + { + Name: "GET_CUSTOMER_RENTAL_HISTORY", + Signature: "INT", + }, ], }, ], @@ -72,11 +107,11 @@ test("find all schemas", () => { test("find named schema", () => { const result = find(obj, "SAKILA"); - const result2 = find(obj, "-/Schemas/SAKILA"); - expect(typeof result).toBe("object"); expect(Array.isArray(result)).toBe(false); expect(result.Name).toBe("SAKILA"); + + const result2 = find(obj, "-/Schemas/SAKILA"); expect(result).toEqual(result2); }); @@ -115,15 +150,26 @@ test("find component by name", () => { expect(typeof result).toBe("object"); expect(Array.isArray(result)).toBe(false); expect(result.Name).toBe("GET_CUSTOMER"); - expect(result.Schema).toBe(""); }); test("find homonyms components", () => { - const result = find(obj, "-/Tables/USER/Columns"); + const result = find(obj, "-/Tables/USER"); expect(Array.isArray(result)).toBe(true); expect(result.length).toBe(2); expect(result[0].Name).toBe("USER"); expect(result[1].Name).toBe("USER"); + + const result2 = find(obj, "-/Tables/USER/Columns"); + expect(result2).toBe(undefined); + + const result3 = find( + obj, + "SAKILA/Packages/CUSTOMERS/Functions/GET_CUSTOMER_RENTAL_HISTORY", + ); + expect(Array.isArray(result3)).toBe(true); + expect(result3.length).toBe(2); + expect(result3[0].Name).toBe("GET_CUSTOMER_RENTAL_HISTORY"); + expect(result3[1].Name).toBe("GET_CUSTOMER_RENTAL_HISTORY"); }); describe("find non-component attribute", () => { @@ -142,3 +188,15 @@ describe("find non-component attribute", () => { expect(result).toBe(undefined); }); }); + +test("find indexes for all tables", () => { + // for all schemas + const result = find(obj, "-/Tables/-/Indexes"); + expect(result.length).toBe(3); + expect(Array.isArray(result)).toBe(true); + + // for a given schema + const result2 = find(obj, "SAKILA/Tables/-/Indexes"); + expect(result2.length).toBe(2); + expect(Array.isArray(result)).toBe(true); +}); -- GitLab From 4e7145203b5c2afcd79d09545011ee1ef1af7dc5 Mon Sep 17 00:00:00 2001 From: Pierre GIRAUD Date: Thu, 9 Oct 2025 08:52:28 +0200 Subject: [PATCH 2/2] ui: Add path to typeMetadata It is now possible to have items in the drawer for types that are not defined at the root level of the model but instead subcomponents with a more complex path (like Indexes). --- internal/cmd/ui/src/components/Drawer.vue | 21 +++++------ internal/cmd/ui/src/dbobjects.js | 45 ++++++++++++++++++++--- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/internal/cmd/ui/src/components/Drawer.vue b/internal/cmd/ui/src/components/Drawer.vue index 4af443c59..65642c50c 100644 --- a/internal/cmd/ui/src/components/Drawer.vue +++ b/internal/cmd/ui/src/components/Drawer.vue @@ -39,18 +39,10 @@ :prepend-icon="mdiDatabase" > -