@@ -138,7 +138,7 @@ INSERT INTO COMMAND_ARG (
138138// Attribute table needs to be unique based on:
139139// UNIQUE("CLUSTER_REF", "PACKAGE_REF", "CODE", "MANUFACTURER_CODE")
140140const INSERT_ATTRIBUTE_QUERY = `
141- INSERT OR REPLACE INTO ATTRIBUTE (
141+ INSERT INTO ATTRIBUTE (
142142 CLUSTER_REF,
143143 PACKAGE_REF,
144144 CODE,
@@ -360,6 +360,50 @@ function argMap(cmdId, packageId, args) {
360360 packageId
361361 ] )
362362}
363+ /**
364+ * Filters out duplicates in an array of objects based on specified keys and logs a warning for each duplicate found.
365+ * This function is used to filter out duplicates in command, attribute, and event data before inserting into the database.
366+ * Treats `null` and `0` as equivalent.
367+ *
368+ * @param {* } db
369+ * @param {* } packageId
370+ * @param {Array } data - Array of objects.
371+ * @param {Array } keys - Array of keys to compare for duplicates (e.g., ['code', 'manufacturerCode']).
372+ * @param {* } elementName
373+ * @returns {Array } - Array of unique objects (duplicates removed).
374+ */
375+ function filterDuplicates ( db , packageId , data , keys , elementName ) {
376+ let seen = new Map ( )
377+ let uniqueItems = [ ]
378+
379+ data . forEach ( ( item , index ) => {
380+ let anyKeysPresent = keys . some ( ( key ) => key in item )
381+
382+ if ( ! anyKeysPresent ) {
383+ // If all keys are missing, treat this item as unique
384+ uniqueItems . push ( item )
385+ } else {
386+ let uniqueKey = keys
387+ . map ( ( key ) => ( item [ key ] === null || item [ key ] === 0 ? 0 : item [ key ] ) )
388+ . join ( '|' )
389+
390+ if ( seen . has ( uniqueKey ) ) {
391+ // Log a warning with the duplicate information
392+ queryNotification . setNotification (
393+ db ,
394+ 'ERROR' ,
395+ `Duplicate ${ elementName } found: ${ JSON . stringify ( item ) } ` ,
396+ packageId
397+ )
398+ } else {
399+ seen . set ( uniqueKey , true )
400+ uniqueItems . push ( item )
401+ }
402+ }
403+ } )
404+
405+ return uniqueItems
406+ }
363407
364408/**
365409 * access data is array of objects, containing id/op/role/modifier.
@@ -647,81 +691,101 @@ async function insertGlobals(db, packageId, data) {
647691 * @returns Promise of cluster extension insertion.
648692 */
649693async function insertClusterExtensions ( db , packageId , knownPackages , data ) {
650- return dbApi
651- . dbMultiSelect (
652- db ,
653- `SELECT CLUSTER_ID FROM CLUSTER WHERE PACKAGE_REF IN (${ dbApi . toInClause (
654- knownPackages
655- ) } ) AND CODE = ?`,
656- data . map ( ( cluster ) => [ cluster . code ] )
657- )
658- . then ( ( rows ) => {
659- let commands = {
660- data : [ ] ,
661- args : [ ] ,
662- access : [ ]
663- }
664- let events = {
665- data : [ ] ,
666- fields : [ ] ,
667- access : [ ]
694+ let rows = await dbApi . dbMultiSelect (
695+ db ,
696+ `SELECT CLUSTER_ID FROM CLUSTER WHERE PACKAGE_REF IN (${ dbApi . toInClause (
697+ knownPackages
698+ ) } ) AND CODE = ?`,
699+ data . map ( ( cluster ) => [ cluster . code ] )
700+ )
701+
702+ let commands = {
703+ data : [ ] ,
704+ args : [ ] ,
705+ access : [ ]
706+ }
707+ let events = {
708+ data : [ ] ,
709+ fields : [ ] ,
710+ access : [ ]
711+ }
712+ let attributes = {
713+ data : [ ] ,
714+ access : [ ]
715+ }
716+
717+ let i , lastId
718+ for ( i = 0 ; i < rows . length ; i ++ ) {
719+ let row = rows [ i ]
720+ if ( row != null ) {
721+ lastId = row . CLUSTER_ID
722+ // NOTE: This code must stay in sync with insertClusters
723+ if ( 'commands' in data [ i ] ) {
724+ let cmds = filterDuplicates (
725+ db ,
726+ packageId ,
727+ data [ i ] . commands ,
728+ [ 'code' , 'manufacturerCode' , 'source' ] ,
729+ 'command'
730+ ) // Removes any duplicates based of db unique constraint and logs package notification (avoids SQL error)
731+ commands . data . push ( ...commandMap ( lastId , packageId , cmds ) )
732+ commands . args . push ( ...cmds . map ( ( command ) => command . args ) )
733+ commands . access . push ( ...cmds . map ( ( command ) => command . access ) )
668734 }
669- let attributes = {
670- data : [ ] ,
671- access : [ ]
735+ if ( 'attributes' in data [ i ] ) {
736+ let atts = filterDuplicates (
737+ db ,
738+ packageId ,
739+ data [ i ] . attributes ,
740+ [ 'code' , 'manufacturerCode' , 'side' ] ,
741+ 'attribute'
742+ ) // Removes any duplicates based of db unique constraint and logs package notification (avoids SQL error)
743+ attributes . data . push ( ...attributeMap ( lastId , packageId , atts ) )
744+ attributes . access . push ( ...atts . map ( ( at ) => at . access ) )
672745 }
673- let i , lastId
674- for ( i = 0 ; i < rows . length ; i ++ ) {
675- let row = rows [ i ]
676- if ( row != null ) {
677- lastId = row . CLUSTER_ID
678- // NOTE: This code must stay in sync with insertClusters
679- if ( 'commands' in data [ i ] ) {
680- let cmds = data [ i ] . commands
681- commands . data . push ( ...commandMap ( lastId , packageId , cmds ) )
682- commands . args . push ( ...cmds . map ( ( command ) => command . args ) )
683- commands . access . push ( ...cmds . map ( ( command ) => command . access ) )
684- }
685- if ( 'attributes' in data [ i ] ) {
686- let atts = data [ i ] . attributes
687- attributes . data . push ( ...attributeMap ( lastId , packageId , atts ) )
688- attributes . access . push ( ...atts . map ( ( at ) => at . access ) )
689- }
690- if ( 'events' in data [ i ] ) {
691- let evs = data [ i ] . events
692- events . data . push ( ...eventMap ( lastId , packageId , evs ) )
693- events . fields . push ( ...evs . map ( ( event ) => event . fields ) )
694- events . access . push ( ...evs . map ( ( event ) => event . access ) )
695- }
696- } else {
697- // DANGER: We got here because we are adding a cluster extension for a
698- // cluster which is not defined. For eg:
699- // <clusterExtension code="0x0000">
700- // <attribute side="server" code="0x4000" define="SW_BUILD_ID"
701- // type="CHAR_STRING" length="16" writable="false"
702- // default="" optional="true"
703- // introducedIn="zll-1.0-11-0037-10">sw build id</attribute>
704- // </clusterExtension>
705- // If a cluster with code 0x0000 does not exist then we run into this
706- // issue.
707- let message = `Attempting to insert cluster extension for a cluster which does not
708- exist. Check clusterExtension meta data in xml file.
709- Cluster Code: ${ data [ i ] . code } `
710- env . logWarning ( message )
711- queryNotification . setNotification (
712- db ,
713- 'WARNING' ,
714- message ,
715- packageId ,
716- 2
717- )
718- }
746+ if ( 'events' in data [ i ] ) {
747+ let evs = filterDuplicates (
748+ db ,
749+ packageId ,
750+ data [ i ] . events ,
751+ [ 'code' , 'manufacturerCode' ] ,
752+ 'event'
753+ ) // Removes any duplicates based of db unique constraint and logs package notification (avoids SQL error)
754+ events . data . push ( ...eventMap ( lastId , packageId , evs ) )
755+ events . fields . push ( ...evs . map ( ( event ) => event . fields ) )
756+ events . access . push ( ...evs . map ( ( event ) => event . access ) )
719757 }
720- let pCommand = insertCommands ( db , packageId , commands )
721- let pAttribute = insertAttributes ( db , packageId , attributes )
722- let pEvent = insertEvents ( db , packageId , events )
723- return Promise . all ( [ pCommand , pAttribute , pEvent ] )
724- } )
758+ } else {
759+ // DANGER: We got here because we are adding a cluster extension for a
760+ // cluster which is not defined. For eg:
761+ // <clusterExtension code="0x0000">
762+ // <attribute side="server" code="0x4000" define="SW_BUILD_ID"
763+ // type="CHAR_STRING" length="16" writable="false"
764+ // default="" optional="true"
765+ // introducedIn="zll-1.0-11-0037-10">sw build id</attribute>
766+ // </clusterExtension>
767+ // If a cluster with code 0x0000 does not exist then we run into this
768+ // issue.
769+ let message = `Attempting to insert cluster extension for a cluster which does not
770+ exist. Check clusterExtension meta data in xml file.
771+ Cluster Code: ${ data [ i ] . code } `
772+ env . logWarning ( message )
773+ queryNotification . setNotification ( db , 'WARNING' , message , packageId , 2 )
774+ }
775+ }
776+
777+ let pCommand = insertCommands ( db , packageId , commands )
778+ let pAttribute = insertAttributes ( db , packageId , attributes )
779+ let pEvent = insertEvents ( db , packageId , events )
780+ return Promise . all ( [ pCommand , pAttribute , pEvent ] ) . catch ( ( err ) => {
781+ if ( err . includes ( 'SQLITE_CONSTRAINT' ) && err . includes ( 'UNIQUE' ) ) {
782+ env . logDebug (
783+ `CRC match for file with package id ${ packageId } , skipping parsing.`
784+ )
785+ } else {
786+ throw err
787+ }
788+ } )
725789}
726790
727791/**
@@ -783,17 +847,38 @@ async function insertClusters(db, packageId, data) {
783847 // NOTE: This code must stay in sync with insertClusterExtensions
784848 if ( 'commands' in data [ i ] ) {
785849 let cmds = data [ i ] . commands
850+ cmds = filterDuplicates (
851+ db ,
852+ packageId ,
853+ cmds ,
854+ [ 'code' , 'manufacturerCode' , 'source' ] ,
855+ 'command'
856+ ) // Removes any duplicates based of db unique constraint and logs package notification (avoids SQL error)
786857 commands . data . push ( ...commandMap ( lastId , packageId , cmds ) )
787858 commands . args . push ( ...cmds . map ( ( command ) => command . args ) )
788859 commands . access . push ( ...cmds . map ( ( command ) => command . access ) )
789860 }
790861 if ( 'attributes' in data [ i ] ) {
791862 let atts = data [ i ] . attributes
863+ atts = filterDuplicates (
864+ db ,
865+ packageId ,
866+ atts ,
867+ [ 'code' , 'manufacturerCode' , 'side' ] ,
868+ 'attribute'
869+ ) // Removes any duplicates based of db unique constraint and logs package notification (avoids SQL error)
792870 attributes . data . push ( ...attributeMap ( lastId , packageId , atts ) )
793871 attributes . access . push ( ...atts . map ( ( at ) => at . access ) )
794872 }
795873 if ( 'events' in data [ i ] ) {
796874 let evs = data [ i ] . events
875+ evs = filterDuplicates (
876+ db ,
877+ packageId ,
878+ evs ,
879+ [ 'code' , 'manufacturerCode' ] ,
880+ 'event'
881+ ) // Removes any duplicates based of db unique constraint and logs package notification (avoids SQL error)
797882 events . data . push ( ...eventMap ( lastId , packageId , evs ) )
798883 events . fields . push ( ...evs . map ( ( event ) => event . fields ) )
799884 events . access . push ( ...evs . map ( ( event ) => event . access ) )
0 commit comments