From 6ee9349405516ff2d9aaefa951effa0d84f27279 Mon Sep 17 00:00:00 2001 From: Paul Regan Date: Fri, 9 Aug 2024 17:04:31 -0400 Subject: [PATCH 01/19] phase 1, working code. needs work --- src-electron/db/query-package.js | 44 ++++++++++++++++++++++---------- src-electron/sdk/matter.js | 3 +-- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src-electron/db/query-package.js b/src-electron/db/query-package.js index 9f9a52cd01..0b8768e618 100644 --- a/src-electron/db/query-package.js +++ b/src-electron/db/query-package.js @@ -692,20 +692,38 @@ async function getAllPackages(db) { } async function getAttributeAccessInterface(db, code) { + const extendedQuery = ` + SELECT + po.PACKAGE_REF, + po.OPTION_CATEGORY, + po.OPTION_CODE, + po.OPTION_LABEL + FROM + PACKAGE_OPTION po + WHERE + po.OPTION_CODE = ? + + UNION ALL + + SELECT + a.PACKAGE_REF, + c.NAME AS OPTION_CATEGORY, + a.STORAGE_POLICY AS OPTION_CODE, + a.NAME AS OPTION_LABEL + FROM + ATTRIBUTE a + LEFT JOIN CLUSTER c ON a.CLUSTER_REF = c.CLUSTER_ID + WHERE + a.STORAGE_POLICY = ? + AND NOT EXISTS ( + SELECT 1 + FROM PACKAGE_OPTION po + WHERE po.OPTION_LABEL = a.NAME + ) + ` + return dbApi - .dbAll( - db, - `SELECT - PACKAGE_REF, - OPTION_CATEGORY, - OPTION_CODE, - OPTION_LABEL - FROM - PACKAGE_OPTION - WHERE - OPTION_CODE = ?`, - [code] - ) + .dbAll(db, extendedQuery, [code, code]) // Note the [code, code] to match both placeholders .then((rows) => rows.map(dbMapping.map.options)) } diff --git a/src-electron/sdk/matter.js b/src-electron/sdk/matter.js index 3db78597f5..87d5e3cc4f 100644 --- a/src-electron/sdk/matter.js +++ b/src-electron/sdk/matter.js @@ -18,7 +18,6 @@ const dbApi = require('../db/db-api.js') const queryPackage = require('../db/query-package.js') const queryCluster = require('../db/query-cluster.js') -const queryAttribute = require('../db/query-attribute.js') const dbEnum = require('../../src-shared/db-enum.js') /** @@ -33,7 +32,7 @@ const dbEnum = require('../../src-shared/db-enum.js') */ async function getForcedExternalStorage(db) { - let forcedExternal = queryPackage.getAttributeAccessInterface( + let forcedExternal = await queryPackage.getAttributeAccessInterface( db, dbEnum.storagePolicy.attributeAccessInterface ) From c20e37763b21fab1b9c2d030302a1838b8c570cc Mon Sep 17 00:00:00 2001 From: Paul Regan Date: Fri, 9 Aug 2024 17:18:28 -0400 Subject: [PATCH 02/19] fix performance issues --- src-electron/db/query-package.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src-electron/db/query-package.js b/src-electron/db/query-package.js index 0b8768e618..28b8678992 100644 --- a/src-electron/db/query-package.js +++ b/src-electron/db/query-package.js @@ -715,11 +715,7 @@ async function getAttributeAccessInterface(db, code) { LEFT JOIN CLUSTER c ON a.CLUSTER_REF = c.CLUSTER_ID WHERE a.STORAGE_POLICY = ? - AND NOT EXISTS ( - SELECT 1 - FROM PACKAGE_OPTION po - WHERE po.OPTION_LABEL = a.NAME - ) + ` return dbApi From c99f2ee2dd880457f9e07b50c7f362ef4539aead Mon Sep 17 00:00:00 2001 From: Paul Regan Date: Fri, 9 Aug 2024 17:22:24 -0400 Subject: [PATCH 03/19] clean up and make more clear --- src-electron/zcl/zcl-loader-silabs.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src-electron/zcl/zcl-loader-silabs.js b/src-electron/zcl/zcl-loader-silabs.js index 061c0f3afe..d60ddcc618 100644 --- a/src-electron/zcl/zcl-loader-silabs.js +++ b/src-electron/zcl/zcl-loader-silabs.js @@ -2310,13 +2310,14 @@ async function parseattributeAccessInterfaceAttributes( pkgRef, attributeAccessInterfaceAttributes ) { - // Use forEach for side effects without expecting a return value - Object.keys(attributeAccessInterfaceAttributes).forEach(async (cluster) => { + const clusters = Object.keys(attributeAccessInterfaceAttributes) + for (let i = 0; i < clusters.length; i++) { + const cluster = clusters[i] const values = attributeAccessInterfaceAttributes[cluster] // Prepare the data for insertion - const optionsKeyValues = values.map((optionValue) => ({ + const optionsKeyValues = values.map((attribute) => ({ code: dbEnum.storagePolicy.attributeAccessInterface, - label: optionValue, + label: attribute, })) // Insert the data into the database try { @@ -2329,7 +2330,7 @@ async function parseattributeAccessInterfaceAttributes( } catch (error) { console.error(`Error inserting attributes for cluster ${cluster}:`, error) } - }) + } } /** From fb3e273aa9b315e8d9695efcb061634793a2079b Mon Sep 17 00:00:00 2001 From: Paul Regan Date: Fri, 9 Aug 2024 17:25:00 -0400 Subject: [PATCH 04/19] add more detail to comments --- src-electron/zcl/zcl-loader-silabs.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src-electron/zcl/zcl-loader-silabs.js b/src-electron/zcl/zcl-loader-silabs.js index d60ddcc618..d2279657a1 100644 --- a/src-electron/zcl/zcl-loader-silabs.js +++ b/src-electron/zcl/zcl-loader-silabs.js @@ -2298,6 +2298,9 @@ async function parseBoolOptions(db, pkgRef, booleanCategories) { * by mapping its values to a specific structure and then inserting them into the database using * the insertOptionsKeyValues function. * + * The main purpose of this function is to store cluster/attribute pairs to include global attributes and their cluster pair + * The ATTRIBUTE table has cluster_ref as null for global attributes so this second method was necessary + * * @param {*} db - The database connection object. * @param {*} pkgRef - The package reference id for which the attributes are being parsed. * @param {*} attributeAccessInterfaceAttributes - An object containing the attribute access interface attributes, From faf4ea0e387a5838209619ba0d54a1556d2c3b6f Mon Sep 17 00:00:00 2001 From: Paul Regan Date: Fri, 9 Aug 2024 17:31:49 -0400 Subject: [PATCH 05/19] changing from union all to union --- src-electron/db/query-package.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-electron/db/query-package.js b/src-electron/db/query-package.js index 28b8678992..35b40018aa 100644 --- a/src-electron/db/query-package.js +++ b/src-electron/db/query-package.js @@ -703,7 +703,7 @@ async function getAttributeAccessInterface(db, code) { WHERE po.OPTION_CODE = ? - UNION ALL + UNION SELECT a.PACKAGE_REF, From 05c9a4bc8150e68059f96fc51166f099001af4a6 Mon Sep 17 00:00:00 2001 From: Paul Regan Date: Fri, 9 Aug 2024 17:33:28 -0400 Subject: [PATCH 06/19] adding better comments --- src-electron/db/query-package.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src-electron/db/query-package.js b/src-electron/db/query-package.js index 35b40018aa..8bc28f8bb1 100644 --- a/src-electron/db/query-package.js +++ b/src-electron/db/query-package.js @@ -690,7 +690,17 @@ async function getAllPackages(db) { ) .then((rows) => rows.map(dbMapping.map.package)) } - +/** + * Retrieves attribute access interface options from the database. + * This function executes a query that combines results from the PACKAGE_OPTION and ATTRIBUTE tables, + * filtering by a specific code. It uses UNION to ensure no duplicate rows are returned, even if the same + * code exists in both tables. The results are then mapped to a standard format using dbMapping.map.options. + * + * @param {Object} db - The database connection object. + * @param {string} code - The code used to filter the results from both PACKAGE_OPTION and ATTRIBUTE tables. + * @returns {Promise} A promise that resolves to an array of objects, each representing an option + * with properties: PACKAGE_REF, OPTION_CATEGORY, OPTION_CODE, and OPTION_LABEL. + */ async function getAttributeAccessInterface(db, code) { const extendedQuery = ` SELECT From 1e097aecee5198fb0f2812d54336e35942b07ba1 Mon Sep 17 00:00:00 2001 From: Paul Regan Date: Mon, 12 Aug 2024 16:15:27 -0400 Subject: [PATCH 07/19] add packageId to forced External query so only query the necessary data --- src-electron/db/query-config.js | 5 ++++- src-electron/db/query-impexp.js | 5 ++++- src-electron/db/query-package.js | 6 ++++-- src-electron/db/query-zcl.js | 1 + src-electron/rest/user-data.js | 8 +++++++- src-electron/sdk/matter.js | 18 +++++++++--------- 6 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src-electron/db/query-config.js b/src-electron/db/query-config.js index 95dae5c03e..f6861fb4ea 100644 --- a/src-electron/db/query-config.js +++ b/src-electron/db/query-config.js @@ -235,7 +235,10 @@ async function insertOrUpdateAttributeState( ) staticAttribute.defaultValue = featureMapDefaultValue } - let forcedExternal = await queryUpgrade.getForcedExternalStorage(db) + let forcedExternal = await queryUpgrade.getForcedExternalStorage( + db, + staticAttribute.packageRef + ) staticAttribute.storagePolicy = await queryUpgrade.computeStoragePolicyNewConfig( db, diff --git a/src-electron/db/query-impexp.js b/src-electron/db/query-impexp.js index abab2690aa..f7f2826017 100644 --- a/src-electron/db/query-impexp.js +++ b/src-electron/db/query-impexp.js @@ -737,7 +737,10 @@ WHERE attribute.reportable = false } if (attributeId) { - forcedExternal = await queryUpgrade.getForcedExternalStorage(db) + forcedExternal = await queryUpgrade.getForcedExternalStorage( + db, + packageIds[0] + ) storagePolicy = await queryUpgrade.computeStorageImport( db, cluster.name, diff --git a/src-electron/db/query-package.js b/src-electron/db/query-package.js index 8bc28f8bb1..e4398c239a 100644 --- a/src-electron/db/query-package.js +++ b/src-electron/db/query-package.js @@ -701,7 +701,7 @@ async function getAllPackages(db) { * @returns {Promise} A promise that resolves to an array of objects, each representing an option * with properties: PACKAGE_REF, OPTION_CATEGORY, OPTION_CODE, and OPTION_LABEL. */ -async function getAttributeAccessInterface(db, code) { +async function getAttributeAccessInterface(db, code, packageId) { const extendedQuery = ` SELECT po.PACKAGE_REF, @@ -712,6 +712,7 @@ async function getAttributeAccessInterface(db, code) { PACKAGE_OPTION po WHERE po.OPTION_CODE = ? + AND po.PACKAGE_REF = ? UNION @@ -725,11 +726,12 @@ async function getAttributeAccessInterface(db, code) { LEFT JOIN CLUSTER c ON a.CLUSTER_REF = c.CLUSTER_ID WHERE a.STORAGE_POLICY = ? + AND a.PACKAGE_REF = ? ` return dbApi - .dbAll(db, extendedQuery, [code, code]) // Note the [code, code] to match both placeholders + .dbAll(db, extendedQuery, [code, packageId, code, packageId]) // Note the [code, code] to match both placeholders .then((rows) => rows.map(dbMapping.map.options)) } diff --git a/src-electron/db/query-zcl.js b/src-electron/db/query-zcl.js index b3b18dce25..bff1394982 100644 --- a/src-electron/db/query-zcl.js +++ b/src-electron/db/query-zcl.js @@ -866,6 +866,7 @@ async function selectAttributeByAttributeIdAndClusterRef( ` SELECT A.ATTRIBUTE_ID, + A.PACKAGE_REF, A.CLUSTER_REF, A.CODE, A.MANUFACTURER_CODE, diff --git a/src-electron/rest/user-data.js b/src-electron/rest/user-data.js index 4c7b5b6408..c772cbad68 100644 --- a/src-electron/rest/user-data.js +++ b/src-electron/rest/user-data.js @@ -257,7 +257,13 @@ function httpPostCluster(db) { } function httpPostForcedExternal(db) { return async (request, response) => { - let forcedExternal = await upgrade.getForcedExternalStorage(db) + let sessionId = request.zapSessionId + let packages = await queryPackage.getPackageSessionPackagePairBySessionId( + db, + sessionId + ) + let packageId = packages[0].pkg.id + let forcedExternal = await upgrade.getForcedExternalStorage(db, packageId) response.status(StatusCodes.OK).json(forcedExternal) } } diff --git a/src-electron/sdk/matter.js b/src-electron/sdk/matter.js index 87d5e3cc4f..2f71dd49ee 100644 --- a/src-electron/sdk/matter.js +++ b/src-electron/sdk/matter.js @@ -21,20 +21,20 @@ const queryCluster = require('../db/query-cluster.js') const dbEnum = require('../../src-shared/db-enum.js') /** - * This asynchronous function retrieves and returns the forced external storage options. + * Fetches forced external storage settings based on the given package ID. + * Utilizes the attribute access interface to query storage policies + * associated with the specified package ID. * - * @param {Object} db - The database instance. - * - * The function calls the 'getAttributeAccessInterface' method from 'queryPackage' with - * the database instance and 'attributeAccessInterface' from 'storagePolicy' as parameters. - * The result is assigned to 'forcedExternal'. - * Finally, it returns the 'forcedExternal' options. + * @param {Object} db - Database connection object. + * @param {Number} packageId - The ID of the package to query. + * @returns {Promise} A promise that resolves to an array of forced external storage settings. */ -async function getForcedExternalStorage(db) { +async function getForcedExternalStorage(db, packageId) { let forcedExternal = await queryPackage.getAttributeAccessInterface( db, - dbEnum.storagePolicy.attributeAccessInterface + dbEnum.storagePolicy.attributeAccessInterface, + packageId ) return forcedExternal } From 7544aa8af99c644c5a132a6e4ac3e58ca6e3510b Mon Sep 17 00:00:00 2001 From: Paul Regan Date: Mon, 12 Aug 2024 16:46:38 -0400 Subject: [PATCH 08/19] adding tests for forcedExternal plus matter sdk --- test/matter.test.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 test/matter.test.js diff --git a/test/matter.test.js b/test/matter.test.js new file mode 100644 index 0000000000..379f4cdb68 --- /dev/null +++ b/test/matter.test.js @@ -0,0 +1,32 @@ +const matter = require('../src-electron/sdk/matter.js') +const testUtil = require('./test-util') +const env = require('../src-electron/util/env') +const dbApi = require('../src-electron/db/db-api') +const zclLoader = require('../src-electron/zcl/zcl-loader') + +const testFile = testUtil.matterTestFile.matterTest + +beforeAll(async () => { + env.setDevelopmentEnv() + let file = env.sqliteTestFile('gen-matter-1') + db = await dbApi.initDatabaseAndLoadSchema( + file, + env.schemaFile(), + env.zapVersion() + ) + let ctx = await zclLoader.loadZcl(db, env.builtinMatterZclMetafile()) + zclPackageId = ctx.packageId +}, testUtil.timeout.long()) + +test( + 'forced external', + async () => { + let forcedExternal = await matter.getForcedExternalStorage(db, zclPackageId) + expect(forcedExternal.length).toBeGreaterThan(0) + forcedExternal.forEach((item) => { + expect(item).toHaveProperty('optionLabel') + expect(item).toHaveProperty('optionCategory') + }) + }, + testUtil.timeout.long() +) From c016a4a54cf74ace2e4e18646e4ce1c51129bd02 Mon Sep 17 00:00:00 2001 From: Paul Regan Date: Mon, 12 Aug 2024 16:59:01 -0400 Subject: [PATCH 09/19] cleanup and add comments --- src-electron/rest/user-data.js | 12 ++++++++++++ test/matter.test.js | 2 -- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src-electron/rest/user-data.js b/src-electron/rest/user-data.js index c772cbad68..a4340532a9 100644 --- a/src-electron/rest/user-data.js +++ b/src-electron/rest/user-data.js @@ -255,6 +255,17 @@ function httpPostCluster(db) { } } } + +/** + * Handles a POST request to retrieve forced external storage options. + * + * This function is designed to be used as a middleware in an Express.js route. It extracts the session ID from the request, + * queries the database for package information associated with that session, and then retrieves forced external storage + * options for the identified package. The results are sent back to the client as a JSON response. + * + * @param {Object} db - The database connection object. + * @returns {Function} An asynchronous function that takes Express.js request and response objects. + */ function httpPostForcedExternal(db) { return async (request, response) => { let sessionId = request.zapSessionId @@ -267,6 +278,7 @@ function httpPostForcedExternal(db) { response.status(StatusCodes.OK).json(forcedExternal) } } + /** * HTTP POST attribute update * diff --git a/test/matter.test.js b/test/matter.test.js index 379f4cdb68..4280e428e7 100644 --- a/test/matter.test.js +++ b/test/matter.test.js @@ -4,8 +4,6 @@ const env = require('../src-electron/util/env') const dbApi = require('../src-electron/db/db-api') const zclLoader = require('../src-electron/zcl/zcl-loader') -const testFile = testUtil.matterTestFile.matterTest - beforeAll(async () => { env.setDevelopmentEnv() let file = env.sqliteTestFile('gen-matter-1') From 43f14690a7c6fdb72072b19d05b542829424e401 Mon Sep 17 00:00:00 2001 From: Paul Regan Date: Mon, 12 Aug 2024 17:01:03 -0400 Subject: [PATCH 10/19] more cleanup --- src-electron/db/query-package.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src-electron/db/query-package.js b/src-electron/db/query-package.js index e4398c239a..5b0f08ab18 100644 --- a/src-electron/db/query-package.js +++ b/src-electron/db/query-package.js @@ -692,19 +692,20 @@ async function getAllPackages(db) { } /** * Retrieves attribute access interface options from the database. - * This function executes a query that combines results from the PACKAGE_OPTION and ATTRIBUTE tables, - * filtering by a specific code. It uses UNION to ensure no duplicate rows are returned, even if the same - * code exists in both tables. The results are then mapped to a standard format using dbMapping.map.options. + * + * This function performs a complex query to fetch options related to a specific code and package ID. It combines results from + * the PACKAGE_OPTION table with those from ATTRIBUTE and CLUSTER tables using a UNION. The purpose is to gather a comprehensive + * list of options that include both direct package options and those inferred from attributes' storage policies and their associated + * clusters. * * @param {Object} db - The database connection object. - * @param {string} code - The code used to filter the results from both PACKAGE_OPTION and ATTRIBUTE tables. - * @returns {Promise} A promise that resolves to an array of objects, each representing an option - * with properties: PACKAGE_REF, OPTION_CATEGORY, OPTION_CODE, and OPTION_LABEL. + * @param {string} code - The option code or storage policy code to query for. + * @param {number} packageId - The ID of the package to which the options are related. + * @returns {Promise} A promise that resolves to an array of option objects, each containing the option category, code, and label. */ async function getAttributeAccessInterface(db, code, packageId) { const extendedQuery = ` SELECT - po.PACKAGE_REF, po.OPTION_CATEGORY, po.OPTION_CODE, po.OPTION_LABEL @@ -717,7 +718,6 @@ async function getAttributeAccessInterface(db, code, packageId) { UNION SELECT - a.PACKAGE_REF, c.NAME AS OPTION_CATEGORY, a.STORAGE_POLICY AS OPTION_CODE, a.NAME AS OPTION_LABEL @@ -729,7 +729,6 @@ async function getAttributeAccessInterface(db, code, packageId) { AND a.PACKAGE_REF = ? ` - return dbApi .dbAll(db, extendedQuery, [code, packageId, code, packageId]) // Note the [code, code] to match both placeholders .then((rows) => rows.map(dbMapping.map.options)) From 41bda057120728ea9c3b5b955e378c164c026b15 Mon Sep 17 00:00:00 2001 From: Paul Regan Date: Mon, 12 Aug 2024 17:09:06 -0400 Subject: [PATCH 11/19] add more unit tests for matter file --- test/matter.test.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/matter.test.js b/test/matter.test.js index 4280e428e7..6de2021188 100644 --- a/test/matter.test.js +++ b/test/matter.test.js @@ -3,6 +3,7 @@ const testUtil = require('./test-util') const env = require('../src-electron/util/env') const dbApi = require('../src-electron/db/db-api') const zclLoader = require('../src-electron/zcl/zcl-loader') +const dbEnum = require('../src-shared/db-enum.js') beforeAll(async () => { env.setDevelopmentEnv() @@ -28,3 +29,13 @@ test( }, testUtil.timeout.long() ) +test('compute storage policy new config', async () => { + const result1 = await matter.computeStorageOptionNewConfig( + dbEnum.storagePolicy.attributeAccessInterface + ) + expect(result1).toEqual(dbEnum.storageOption.external) + const result2 = await matter.computeStorageOptionNewConfig( + dbEnum.storagePolicy.any + ) + expect(result2).toEqual(dbEnum.storageOption.ram) +}) From ff9f91a10d182ac8b672cb1a28e20c43223b51ea Mon Sep 17 00:00:00 2001 From: Paul Regan Date: Tue, 13 Aug 2024 10:18:10 -0400 Subject: [PATCH 12/19] adding packageId to computeStoragePolicy helper --- src-electron/generator/helper-zcl.js | 9 ++++++--- src-electron/sdk/matter.js | 5 +++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src-electron/generator/helper-zcl.js b/src-electron/generator/helper-zcl.js index bcb045daed..c954f8ef8e 100644 --- a/src-electron/generator/helper-zcl.js +++ b/src-electron/generator/helper-zcl.js @@ -771,7 +771,8 @@ async function zcl_attributes(options) { attributes = await upgrade.computeStoragePolicyForGlobalAttributes( this.global.db, this.id, - attributes + attributes, + packageIds[0] ) } else { attributes = await queryZcl.selectAllAttributes(this.global.db, packageIds) @@ -805,7 +806,8 @@ async function zcl_attributes_client(options) { clientAttributes = await upgrade.computeStoragePolicyForGlobalAttributes( this.global.db, this.id, - clientAttributes + clientAttributes, + packageIds[0] ) } else { clientAttributes = await queryZcl.selectAllAttributesBySide( @@ -846,7 +848,8 @@ async function zcl_attributes_server(options) { serverAttributes = await upgrade.computeStoragePolicyForGlobalAttributes( this.global.db, this.id, - serverAttributes + serverAttributes, + packageIds[0] ) } else { serverAttributes = await queryZcl.selectAllAttributesBySide( diff --git a/src-electron/sdk/matter.js b/src-electron/sdk/matter.js index 2f71dd49ee..e47936a542 100644 --- a/src-electron/sdk/matter.js +++ b/src-electron/sdk/matter.js @@ -58,14 +58,15 @@ async function getForcedExternalStorage(db, packageId) { async function computeStoragePolicyForGlobalAttributes( db, clusterId, - attributes + attributes, + packageId ) { let forcedExternal let clusterName = await queryCluster.selectClusterName(db, clusterId) return Promise.all( attributes.map(async (attribute) => { if (attribute.clusterId == null) { - forcedExternal = await getForcedExternalStorage(db, attribute.id) + forcedExternal = await getForcedExternalStorage(db, packageId) forcedExternal.some((option) => { if ( option.optionCategory == clusterName && From 47d8c1def51164626674fd3fa77ad538251207b0 Mon Sep 17 00:00:00 2001 From: Paul Regan Date: Tue, 13 Aug 2024 10:23:57 -0400 Subject: [PATCH 13/19] cleanup, add try/catch and better comments --- src-electron/db/query-package.js | 56 +++++----- src-electron/sdk/matter.js | 153 +++++++++++++++----------- src-electron/zcl/zcl-loader-silabs.js | 2 +- 3 files changed, 122 insertions(+), 89 deletions(-) diff --git a/src-electron/db/query-package.js b/src-electron/db/query-package.js index 5b0f08ab18..fbde0829ca 100644 --- a/src-electron/db/query-package.js +++ b/src-electron/db/query-package.js @@ -704,34 +704,38 @@ async function getAllPackages(db) { * @returns {Promise} A promise that resolves to an array of option objects, each containing the option category, code, and label. */ async function getAttributeAccessInterface(db, code, packageId) { - const extendedQuery = ` - SELECT - po.OPTION_CATEGORY, - po.OPTION_CODE, - po.OPTION_LABEL - FROM - PACKAGE_OPTION po - WHERE - po.OPTION_CODE = ? - AND po.PACKAGE_REF = ? + try { + const extendedQuery = ` + SELECT + po.OPTION_CATEGORY, + po.OPTION_CODE, + po.OPTION_LABEL + FROM + PACKAGE_OPTION po + WHERE + po.OPTION_CODE = ? + AND po.PACKAGE_REF = ? - UNION + UNION - SELECT - c.NAME AS OPTION_CATEGORY, - a.STORAGE_POLICY AS OPTION_CODE, - a.NAME AS OPTION_LABEL - FROM - ATTRIBUTE a - LEFT JOIN CLUSTER c ON a.CLUSTER_REF = c.CLUSTER_ID - WHERE - a.STORAGE_POLICY = ? - AND a.PACKAGE_REF = ? - - ` - return dbApi - .dbAll(db, extendedQuery, [code, packageId, code, packageId]) // Note the [code, code] to match both placeholders - .then((rows) => rows.map(dbMapping.map.options)) + SELECT + c.NAME AS OPTION_CATEGORY, + a.STORAGE_POLICY AS OPTION_CODE, + a.NAME AS OPTION_LABEL + FROM + ATTRIBUTE a + LEFT JOIN CLUSTER c ON a.CLUSTER_REF = c.CLUSTER_ID + WHERE + a.STORAGE_POLICY = ? + AND a.PACKAGE_REF = ? + ` + return dbApi + .dbAll(db, extendedQuery, [code, packageId, code, packageId]) // Note the [code, packageId, code, packageId] to match both placeholders + .then((rows) => rows.map(dbMapping.map.options)) + } catch (error) { + console.error('Error fetching attribute access interface:', error) + throw error // Rethrow the error for further handling if necessary + } } /** diff --git a/src-electron/sdk/matter.js b/src-electron/sdk/matter.js index e47936a542..1e37a6a284 100644 --- a/src-electron/sdk/matter.js +++ b/src-electron/sdk/matter.js @@ -31,27 +31,33 @@ const dbEnum = require('../../src-shared/db-enum.js') */ async function getForcedExternalStorage(db, packageId) { - let forcedExternal = await queryPackage.getAttributeAccessInterface( - db, - dbEnum.storagePolicy.attributeAccessInterface, - packageId - ) - return forcedExternal + try { + let forcedExternal = await queryPackage.getAttributeAccessInterface( + db, + dbEnum.storagePolicy.attributeAccessInterface, + packageId + ) + return forcedExternal + } catch (error) { + console.error('Error fetching forced external storage:', error) + throw error // Optionally re-throw the error for further handling + } } /** - * This function takes a clusterId (the database ID, not the specification-defined ID) and an array of attributes (associated with the database defined clusterID) - * and changes the global attributes (attributes with specification defined clusterId = null) to represent storage policy - * based on the cluster/attribute pair in zcl.json + * This function takes a clusterId (the database ID, not the specification-defined ID), an array of attributes (associated with the database defined clusterID), + * and a packageId to identify the specific package the attributes belong to. It changes the global attributes (attributes with specification defined clusterId = null) to represent storage policy + * based on the cluster/attribute pair in zcl.json. * - * Although the specification defined clusterID of the attriibute is null indicating it is a global attribute, we know what the database defined clusterID is by what is passed in as a parameter. + * Although the specification defined clusterID of the attribute is null indicating it is a global attribute, we know what the database defined clusterID is by what is passed in as a parameter. * - * That database defined clusterID is used to query the name of the cluster which is in turn used to compute the storage policy for that cluster/attribute pair. + * That database defined clusterID is used to query the name of the cluster which is in turn used to compute the storage policy for that cluster/attribute pair based on the packageId. * * @export * @param {*} db * @param {*} clusterId (database defined) the clusterId representing a cluster from the database being used in the application * @param {*} attributes an array of objects representing the attributes associated with the cluster + * @param {*} packageId the ID of the package to which the attributes belong, used to determine storage policies specific to the package * @returns an array of objects representing attributes in the database */ @@ -61,26 +67,34 @@ async function computeStoragePolicyForGlobalAttributes( attributes, packageId ) { - let forcedExternal - let clusterName = await queryCluster.selectClusterName(db, clusterId) - return Promise.all( - attributes.map(async (attribute) => { - if (attribute.clusterId == null) { - forcedExternal = await getForcedExternalStorage(db, packageId) - forcedExternal.some((option) => { - if ( - option.optionCategory == clusterName && - option.optionLabel == attribute.name - ) { - attribute.storagePolicy = - dbEnum.storagePolicy.attributeAccessInterface - return true - } - }) - } - return attribute - }) - ) + try { + let forcedExternal + let clusterName = await queryCluster.selectClusterName(db, clusterId) + return Promise.all( + attributes.map(async (attribute) => { + if (attribute.clusterId == null) { + forcedExternal = await getForcedExternalStorage(db, packageId) + forcedExternal.some((option) => { + if ( + option.optionCategory == clusterName && + option.optionLabel == attribute.name + ) { + attribute.storagePolicy = + dbEnum.storagePolicy.attributeAccessInterface + return true + } + }) + } + return attribute + }) + ) + } catch (error) { + console.error( + 'Failed to compute storage policy for global attributes:', + error + ) + throw error // Rethrow the error if you want to handle it further up the call stack + } } /** * This asynchronous function computes and returns the new configuration for a storage option. @@ -95,15 +109,20 @@ async function computeStoragePolicyForGlobalAttributes( */ async function computeStorageOptionNewConfig(storagePolicy) { - let storageOption - if (storagePolicy == dbEnum.storagePolicy.attributeAccessInterface) { - storageOption = dbEnum.storageOption.external - } else if (storagePolicy == dbEnum.storagePolicy.any) { - storageOption = dbEnum.storageOption.ram - } else { - throw 'check storage policy' + try { + let storageOption + if (storagePolicy == dbEnum.storagePolicy.attributeAccessInterface) { + storageOption = dbEnum.storageOption.external + } else if (storagePolicy == dbEnum.storagePolicy.any) { + storageOption = dbEnum.storageOption.ram + } else { + throw new Error('Invalid storage policy') + } + return storageOption + } catch (error) { + console.error('Error computing new storage option config:', error) + throw error // Rethrow the error for further handling if necessary } - return storageOption } /** * This asynchronous function computes and returns the new configuration for a storage policy. @@ -126,17 +145,22 @@ async function computeStoragePolicyNewConfig( forcedExternal, attributeName ) { - let clusterName = await queryCluster.selectClusterName(db, clusterRef) - forcedExternal.some((option) => { - if ( - option.optionCategory == clusterName && - option.optionLabel == attributeName - ) { - storagePolicy = dbEnum.storagePolicy.attributeAccessInterface - return true - } - }) - return storagePolicy + try { + let clusterName = await queryCluster.selectClusterName(db, clusterRef) + forcedExternal.some((option) => { + if ( + option.optionCategory == clusterName && + option.optionLabel == attributeName + ) { + storagePolicy = dbEnum.storagePolicy.attributeAccessInterface + return true + } + }) + return storagePolicy + } catch (error) { + console.error('Error computing storage policy new config:', error) + throw error // Rethrow the error for further handling if necessary + } } /** @@ -162,18 +186,23 @@ async function computeStorageImport( forcedExternal, attributeName ) { - let updatedStoragePolicy = storagePolicy - forcedExternal.some((option) => { - if ( - option.optionCategory == clusterName && - option.optionLabel == attributeName - ) { - updatedStoragePolicy = dbEnum.storagePolicy.attributeAccessInterface - return true - } - return false - }) - return updatedStoragePolicy + try { + let updatedStoragePolicy = storagePolicy + forcedExternal.some((option) => { + if ( + option.optionCategory == clusterName && + option.optionLabel == attributeName + ) { + updatedStoragePolicy = dbEnum.storagePolicy.attributeAccessInterface + return true + } + return false + }) + return updatedStoragePolicy + } catch (error) { + console.error('Error computing storage import:', error) + throw error // Rethrow the error for further handling if necessary + } } exports.getForcedExternalStorage = getForcedExternalStorage diff --git a/src-electron/zcl/zcl-loader-silabs.js b/src-electron/zcl/zcl-loader-silabs.js index d2279657a1..bea64d55c5 100644 --- a/src-electron/zcl/zcl-loader-silabs.js +++ b/src-electron/zcl/zcl-loader-silabs.js @@ -2298,7 +2298,7 @@ async function parseBoolOptions(db, pkgRef, booleanCategories) { * by mapping its values to a specific structure and then inserting them into the database using * the insertOptionsKeyValues function. * - * The main purpose of this function is to store cluster/attribute pairs to include global attributes and their cluster pair + * The main purpose of this function is to store cluster/attribute pairs including global attributes and their cluster pair * The ATTRIBUTE table has cluster_ref as null for global attributes so this second method was necessary * * @param {*} db - The database connection object. From 155d7b9ebe896fdd8d158abb15a9fcd039fa5b3f Mon Sep 17 00:00:00 2001 From: Paul Regan Date: Tue, 13 Aug 2024 15:51:08 -0400 Subject: [PATCH 14/19] allowing for an array of packageIds to be passed --- src-electron/db/query-impexp.js | 5 +---- src-electron/db/query-package.js | 22 ++++++++++++++++++---- src-electron/generator/helper-zcl.js | 6 +++--- src-electron/sdk/matter.js | 8 ++++---- 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src-electron/db/query-impexp.js b/src-electron/db/query-impexp.js index f7f2826017..0901690d9d 100644 --- a/src-electron/db/query-impexp.js +++ b/src-electron/db/query-impexp.js @@ -737,10 +737,7 @@ WHERE attribute.reportable = false } if (attributeId) { - forcedExternal = await queryUpgrade.getForcedExternalStorage( - db, - packageIds[0] - ) + forcedExternal = await queryUpgrade.getForcedExternalStorage(db, packageIds) storagePolicy = await queryUpgrade.computeStorageImport( db, cluster.name, diff --git a/src-electron/db/query-package.js b/src-electron/db/query-package.js index fbde0829ca..d79216d80a 100644 --- a/src-electron/db/query-package.js +++ b/src-electron/db/query-package.js @@ -703,8 +703,21 @@ async function getAllPackages(db) { * @param {number} packageId - The ID of the package to which the options are related. * @returns {Promise} A promise that resolves to an array of option objects, each containing the option category, code, and label. */ -async function getAttributeAccessInterface(db, code, packageId) { +async function getAttributeAccessInterface(db, code, packageIds) { try { + let packageRefCondition = `po.PACKAGE_REF = ?` + let attributePackageRefCondition = `a.PACKAGE_REF = ?` + let queryParams = [code, packageIds, code, packageIds] + + // Check if packageIds is an array and adjust the query and parameters accordingly + if (Array.isArray(packageIds)) { + const placeholders = packageIds.map(() => '?').join(', ') + packageRefCondition = `po.PACKAGE_REF IN (${placeholders})` + attributePackageRefCondition = `a.PACKAGE_REF IN (${placeholders})` + // Adjust queryParams for the IN clause + queryParams = [code, ...packageIds, code, ...packageIds] + } + const extendedQuery = ` SELECT po.OPTION_CATEGORY, @@ -714,7 +727,7 @@ async function getAttributeAccessInterface(db, code, packageId) { PACKAGE_OPTION po WHERE po.OPTION_CODE = ? - AND po.PACKAGE_REF = ? + AND ${packageRefCondition} UNION @@ -727,10 +740,11 @@ async function getAttributeAccessInterface(db, code, packageId) { LEFT JOIN CLUSTER c ON a.CLUSTER_REF = c.CLUSTER_ID WHERE a.STORAGE_POLICY = ? - AND a.PACKAGE_REF = ? + AND ${attributePackageRefCondition} ` + return dbApi - .dbAll(db, extendedQuery, [code, packageId, code, packageId]) // Note the [code, packageId, code, packageId] to match both placeholders + .dbAll(db, extendedQuery, queryParams) .then((rows) => rows.map(dbMapping.map.options)) } catch (error) { console.error('Error fetching attribute access interface:', error) diff --git a/src-electron/generator/helper-zcl.js b/src-electron/generator/helper-zcl.js index c954f8ef8e..597efef517 100644 --- a/src-electron/generator/helper-zcl.js +++ b/src-electron/generator/helper-zcl.js @@ -772,7 +772,7 @@ async function zcl_attributes(options) { this.global.db, this.id, attributes, - packageIds[0] + packageIds ) } else { attributes = await queryZcl.selectAllAttributes(this.global.db, packageIds) @@ -807,7 +807,7 @@ async function zcl_attributes_client(options) { this.global.db, this.id, clientAttributes, - packageIds[0] + packageIds ) } else { clientAttributes = await queryZcl.selectAllAttributesBySide( @@ -849,7 +849,7 @@ async function zcl_attributes_server(options) { this.global.db, this.id, serverAttributes, - packageIds[0] + packageIds ) } else { serverAttributes = await queryZcl.selectAllAttributesBySide( diff --git a/src-electron/sdk/matter.js b/src-electron/sdk/matter.js index 1e37a6a284..d430628b33 100644 --- a/src-electron/sdk/matter.js +++ b/src-electron/sdk/matter.js @@ -30,12 +30,12 @@ const dbEnum = require('../../src-shared/db-enum.js') * @returns {Promise} A promise that resolves to an array of forced external storage settings. */ -async function getForcedExternalStorage(db, packageId) { +async function getForcedExternalStorage(db, packageIds) { try { let forcedExternal = await queryPackage.getAttributeAccessInterface( db, dbEnum.storagePolicy.attributeAccessInterface, - packageId + packageIds ) return forcedExternal } catch (error) { @@ -65,7 +65,7 @@ async function computeStoragePolicyForGlobalAttributes( db, clusterId, attributes, - packageId + packageIds ) { try { let forcedExternal @@ -73,7 +73,7 @@ async function computeStoragePolicyForGlobalAttributes( return Promise.all( attributes.map(async (attribute) => { if (attribute.clusterId == null) { - forcedExternal = await getForcedExternalStorage(db, packageId) + forcedExternal = await getForcedExternalStorage(db, packageIds) forcedExternal.some((option) => { if ( option.optionCategory == clusterName && From 3996bcf0e876f523ea0e2220f4a061eb53211cf5 Mon Sep 17 00:00:00 2001 From: Paul Regan Date: Tue, 13 Aug 2024 16:00:17 -0400 Subject: [PATCH 15/19] PR review --- src-electron/db/query-package.js | 19 ++++++++++--------- src-electron/rest/user-data.js | 4 ++-- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src-electron/db/query-package.js b/src-electron/db/query-package.js index d79216d80a..da61580465 100644 --- a/src-electron/db/query-package.js +++ b/src-electron/db/query-package.js @@ -705,18 +705,19 @@ async function getAllPackages(db) { */ async function getAttributeAccessInterface(db, code, packageIds) { try { + // Ensure packageIds is always an array + if (!Array.isArray(packageIds)) { + packageIds = [packageIds] + } + let packageRefCondition = `po.PACKAGE_REF = ?` let attributePackageRefCondition = `a.PACKAGE_REF = ?` - let queryParams = [code, packageIds, code, packageIds] + let queryParams = [code, ...packageIds, code, ...packageIds] - // Check if packageIds is an array and adjust the query and parameters accordingly - if (Array.isArray(packageIds)) { - const placeholders = packageIds.map(() => '?').join(', ') - packageRefCondition = `po.PACKAGE_REF IN (${placeholders})` - attributePackageRefCondition = `a.PACKAGE_REF IN (${placeholders})` - // Adjust queryParams for the IN clause - queryParams = [code, ...packageIds, code, ...packageIds] - } + // Since packageIds is now always an array, adjust the query and parameters accordingly + const placeholders = packageIds.map(() => '?').join(', ') + packageRefCondition = `po.PACKAGE_REF IN (${placeholders})` + attributePackageRefCondition = `a.PACKAGE_REF IN (${placeholders})` const extendedQuery = ` SELECT diff --git a/src-electron/rest/user-data.js b/src-electron/rest/user-data.js index a4340532a9..7ecb67b975 100644 --- a/src-electron/rest/user-data.js +++ b/src-electron/rest/user-data.js @@ -273,8 +273,8 @@ function httpPostForcedExternal(db) { db, sessionId ) - let packageId = packages[0].pkg.id - let forcedExternal = await upgrade.getForcedExternalStorage(db, packageId) + let packageIds = packages.map((pkg) => pkg.id) + let forcedExternal = await upgrade.getForcedExternalStorage(db, packageIds) response.status(StatusCodes.OK).json(forcedExternal) } } From 596d2dc936e2f4d75180e29802a2508dd838ea56 Mon Sep 17 00:00:00 2001 From: Paul Regan Date: Tue, 13 Aug 2024 16:07:09 -0400 Subject: [PATCH 16/19] update comments --- src-electron/db/query-package.js | 7 ++++--- src-electron/sdk/matter.js | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src-electron/db/query-package.js b/src-electron/db/query-package.js index da61580465..67fcb5ccec 100644 --- a/src-electron/db/query-package.js +++ b/src-electron/db/query-package.js @@ -693,14 +693,15 @@ async function getAllPackages(db) { /** * Retrieves attribute access interface options from the database. * - * This function performs a complex query to fetch options related to a specific code and package ID. It combines results from + * This function performs a complex query to fetch options related to a specific code and package IDs. It combines results from * the PACKAGE_OPTION table with those from ATTRIBUTE and CLUSTER tables using a UNION. The purpose is to gather a comprehensive * list of options that include both direct package options and those inferred from attributes' storage policies and their associated - * clusters. + * clusters. It supports querying for multiple package IDs by ensuring the packageIds parameter is treated as an array, allowing + * for more flexible queries. * * @param {Object} db - The database connection object. * @param {string} code - The option code or storage policy code to query for. - * @param {number} packageId - The ID of the package to which the options are related. + * @param {number|Array} packageIds - The ID(s) of the package(s) to which the options are related. Can be a single ID or an array of IDs. * @returns {Promise} A promise that resolves to an array of option objects, each containing the option category, code, and label. */ async function getAttributeAccessInterface(db, code, packageIds) { diff --git a/src-electron/sdk/matter.js b/src-electron/sdk/matter.js index d430628b33..c3e6a8f477 100644 --- a/src-electron/sdk/matter.js +++ b/src-electron/sdk/matter.js @@ -26,7 +26,7 @@ const dbEnum = require('../../src-shared/db-enum.js') * associated with the specified package ID. * * @param {Object} db - Database connection object. - * @param {Number} packageId - The ID of the package to query. + * @param {Number} packageIds - The ID of the packages to query. * @returns {Promise} A promise that resolves to an array of forced external storage settings. */ From a6244150ebb5a605615ae68cb8a6a2f6b0bf7d63 Mon Sep 17 00:00:00 2001 From: Paul Regan Date: Tue, 13 Aug 2024 17:44:16 -0400 Subject: [PATCH 17/19] PR review --- src-electron/db/query-package.js | 13 ++++--------- src-electron/sdk/matter.js | 7 ++++++- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src-electron/db/query-package.js b/src-electron/db/query-package.js index 67fcb5ccec..e9ed86d949 100644 --- a/src-electron/db/query-package.js +++ b/src-electron/db/query-package.js @@ -706,19 +706,14 @@ async function getAllPackages(db) { */ async function getAttributeAccessInterface(db, code, packageIds) { try { - // Ensure packageIds is always an array - if (!Array.isArray(packageIds)) { - packageIds = [packageIds] - } - let packageRefCondition = `po.PACKAGE_REF = ?` let attributePackageRefCondition = `a.PACKAGE_REF = ?` let queryParams = [code, ...packageIds, code, ...packageIds] - // Since packageIds is now always an array, adjust the query and parameters accordingly - const placeholders = packageIds.map(() => '?').join(', ') - packageRefCondition = `po.PACKAGE_REF IN (${placeholders})` - attributePackageRefCondition = `a.PACKAGE_REF IN (${placeholders})` + // Use dbApi.toInClause to automatically create the placeholders for the IN clause + const inClause = dbApi.toInClause(packageIds) + packageRefCondition = `po.PACKAGE_REF IN (${inClause})` + attributePackageRefCondition = `a.PACKAGE_REF IN (${inClause})` const extendedQuery = ` SELECT diff --git a/src-electron/sdk/matter.js b/src-electron/sdk/matter.js index c3e6a8f477..875f259f8b 100644 --- a/src-electron/sdk/matter.js +++ b/src-electron/sdk/matter.js @@ -32,10 +32,15 @@ const dbEnum = require('../../src-shared/db-enum.js') async function getForcedExternalStorage(db, packageIds) { try { + // Ensure packageIds is an array + const packageIdsArray = Array.isArray(packageIds) + ? packageIds + : [packageIds] + let forcedExternal = await queryPackage.getAttributeAccessInterface( db, dbEnum.storagePolicy.attributeAccessInterface, - packageIds + packageIdsArray ) return forcedExternal } catch (error) { From 937b3744c90fd5563f8457a87c6b3b1d33e0c373 Mon Sep 17 00:00:00 2001 From: Paul Regan Date: Tue, 13 Aug 2024 17:47:38 -0400 Subject: [PATCH 18/19] fix comments to be more clear and practical --- src-electron/db/query-package.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src-electron/db/query-package.js b/src-electron/db/query-package.js index e9ed86d949..521fa23690 100644 --- a/src-electron/db/query-package.js +++ b/src-electron/db/query-package.js @@ -699,9 +699,11 @@ async function getAllPackages(db) { * clusters. It supports querying for multiple package IDs by ensuring the packageIds parameter is treated as an array, allowing * for more flexible queries. * + * The ATTRIBUTES table is spec only data so PACKAGE_OPTION was used to add SDK data without interfering with the spec data + * * @param {Object} db - The database connection object. * @param {string} code - The option code or storage policy code to query for. - * @param {number|Array} packageIds - The ID(s) of the package(s) to which the options are related. Can be a single ID or an array of IDs. + * @param {Array} packageIds - The ID(s) of the package(s) to which the options are related. Can be a single ID or an array of IDs. * @returns {Promise} A promise that resolves to an array of option objects, each containing the option category, code, and label. */ async function getAttributeAccessInterface(db, code, packageIds) { From bd74f44bdecd5f84ff55a40c4f61aaea5a1b6787 Mon Sep 17 00:00:00 2001 From: Paul Regan Date: Tue, 13 Aug 2024 17:57:33 -0400 Subject: [PATCH 19/19] PR review not working so compromising by passing an array but allowing a Number to be passed as an alternative --- src-electron/db/query-package.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src-electron/db/query-package.js b/src-electron/db/query-package.js index 521fa23690..67fcb5ccec 100644 --- a/src-electron/db/query-package.js +++ b/src-electron/db/query-package.js @@ -699,23 +699,26 @@ async function getAllPackages(db) { * clusters. It supports querying for multiple package IDs by ensuring the packageIds parameter is treated as an array, allowing * for more flexible queries. * - * The ATTRIBUTES table is spec only data so PACKAGE_OPTION was used to add SDK data without interfering with the spec data - * * @param {Object} db - The database connection object. * @param {string} code - The option code or storage policy code to query for. - * @param {Array} packageIds - The ID(s) of the package(s) to which the options are related. Can be a single ID or an array of IDs. + * @param {number|Array} packageIds - The ID(s) of the package(s) to which the options are related. Can be a single ID or an array of IDs. * @returns {Promise} A promise that resolves to an array of option objects, each containing the option category, code, and label. */ async function getAttributeAccessInterface(db, code, packageIds) { try { + // Ensure packageIds is always an array + if (!Array.isArray(packageIds)) { + packageIds = [packageIds] + } + let packageRefCondition = `po.PACKAGE_REF = ?` let attributePackageRefCondition = `a.PACKAGE_REF = ?` let queryParams = [code, ...packageIds, code, ...packageIds] - // Use dbApi.toInClause to automatically create the placeholders for the IN clause - const inClause = dbApi.toInClause(packageIds) - packageRefCondition = `po.PACKAGE_REF IN (${inClause})` - attributePackageRefCondition = `a.PACKAGE_REF IN (${inClause})` + // Since packageIds is now always an array, adjust the query and parameters accordingly + const placeholders = packageIds.map(() => '?').join(', ') + packageRefCondition = `po.PACKAGE_REF IN (${placeholders})` + attributePackageRefCondition = `a.PACKAGE_REF IN (${placeholders})` const extendedQuery = ` SELECT