@@ -27,11 +27,15 @@ func mergeModels(tx boil.Executor, primaryID uint64, secondaryID uint64, foreign
2727 var err error
2828
2929 for _, conflict := range conflictingKeys {
30- err = deleteConflictsBeforeMerge(tx, conflict, primaryID, secondaryID)
31- if err != nil {
32- return err
33- }
34- }
30+ if len (conflict.columns ) == 1 && conflict.columns [0] == conflict.objectIdColumn {
31+ err = deleteOneToOneConflictsBeforeMerge(tx, conflict, primaryID, secondaryID)
32+ } else {
33+ err = deleteOneToManyConflictsBeforeMerge(tx, conflict, primaryID, secondaryID)
34+ }
35+ if err != nil {
36+ return err
37+ }
38+ }
3539
3640 for _, fk := range foreignKeys {
3741 // TODO: use NewQuery here, not plain sql
@@ -48,52 +52,96 @@ func mergeModels(tx boil.Executor, primaryID uint64, secondaryID uint64, foreign
4852 return checkMerge(tx, foreignKeys)
4953}
5054
51- func deleteConflictsBeforeMerge(tx boil.Executor , conflict conflictingUniqueKey, primaryID uint64, secondaryID uint64) error {
52- conflictingColumns := strmangle.SetComplement (conflict.columns , []string{conflict.objectIdColumn })
55+ func deleteOneToOneConflictsBeforeMerge(tx boil.Executor , conflict conflictingUniqueKey, primaryID uint64, secondaryID uint64) error {
56+ query := fmt.Sprintf (
57+ " SELECT COUNT(*) FROM %s WHERE %s IN (%s )" ,
58+ conflict.table , conflict.objectIdColumn ,
59+ strmangle.Placeholders (dialect.IndexPlaceholders , 2, 1, 1),
60+ )
5361
54- if len (conflictingColumns) < 1 {
55- return nil
56- } else if len (conflictingColumns) > 1 {
57- return errors.Err (" this doesnt work for unique keys with more than two columns (yet)" )
62+ var count int
63+ err := tx.QueryRow (query, primaryID, secondaryID).Scan (&count)
64+ if err != nil {
65+ return errors.Err (err)
66+ }
67+
68+ if count > 2 {
69+ return errors.Err (" it should not be possible to have more than two rows here" )
70+ } else if count != 2 {
71+ return nil // no conflicting rows
5872 }
5973
74+ query = fmt.Sprintf (
75+ " DELETE FROM %s WHERE %s = %s " ,
76+ conflict.table , conflict.objectIdColumn , strmangle.Placeholders (dialect.IndexPlaceholders , 1, 1, 1),
77+ )
78+
79+ _, err = tx.Exec (query, secondaryID)
80+ return errors.Err (err)
81+ }
82+
83+ func deleteOneToManyConflictsBeforeMerge(tx boil.Executor , conflict conflictingUniqueKey, primaryID uint64, secondaryID uint64) error {
84+ conflictingColumns := strmangle.SetComplement (conflict.columns , []string{conflict.objectIdColumn })
85+
6086 query := fmt.Sprintf (
6187 " SELECT %s FROM %s WHERE %s IN (%s ) GROUP BY %s HAVING count(distinct %s ) > 1" ,
62- conflictingColumns[0] , conflict.table , conflict.objectIdColumn ,
88+ strings .Join ( conflictingColumns, " , " ) , conflict.table , conflict.objectIdColumn ,
6389 strmangle.Placeholders (dialect.IndexPlaceholders , 2, 1, 1),
64- conflictingColumns[0] , conflict.objectIdColumn ,
90+ strings .Join ( conflictingColumns, " , " ) , conflict.objectIdColumn ,
6591 )
6692
93+ //The selectParams should be the ObjectIDs to search for regarding the conflict.
6794 rows, err := tx.Query (query, primaryID, secondaryID)
68- defer rows.Close ()
6995 if err != nil {
7096 return errors.Err (err)
7197 }
7298
73- args := []interface{}{secondaryID}
99+ //Since we don't don't know if advance how many columns the query returns, we have dynamically assign them to be
100+ // used in the delete query.
101+ colNames, err := rows.Columns ()
102+ if err != nil {
103+ return errors.Err (err)
104+ }
105+ //Each row result of the query needs to be removed for being a conflicting row. Store each row's keys in an array.
106+ var rowsToRemove = [][]interface{}(nil )
74107 for rows.Next () {
75- var value string
76- err = rows.Scan (&value)
108+ //Set pointers for dynamic scan
109+ iColPtrs := make([]interface{}, len (colNames))
110+ for i := 0; i < len (colNames); i++ {
111+ s := string(" " )
112+ iColPtrs[i] = &s
113+ }
114+ //Dynamically scan n columns
115+ err = rows.Scan (iColPtrs... )
77116 if err != nil {
78117 return errors.Err (err)
79118 }
80- args = append(args, value)
81- }
82-
83- // if no rows found, no need to delete anything
84- if len (args) < 2 {
85- return nil
119+ //Grab scanned values for query arguments
120+ iCol := make([]interface{}, len (colNames))
121+ for i, col := range iColPtrs {
122+ x := col. (*string)
123+ iCol[i] = *x
124+ }
125+ rowsToRemove = append(rowsToRemove, iCol)
86126 }
127+ defer rows.Close ()
87128
129+ //This query will adjust dynamically depending on the number of conflicting keys, adding AND expressions for each
130+ // key to ensure the right conflicting rows are deleted.
88131 query = fmt.Sprintf (
89- " DELETE FROM %s WHERE %s = %s AND %s IN ( %s ) " ,
90- conflict.table , conflict .objectIdColumn , strmangle .Placeholders (dialect .IndexPlaceholders , 1, 1, 1),
91- conflictingColumns[0], strmangle .Placeholders (dialect .IndexPlaceholders , len (args)-1, 2, 1) ,
132+ " DELETE FROM %s %s " ,
133+ conflict.table ,
134+ " WHERE " +strings .Join (conflict .columns , " = ? AND " )+ " = ? " ,
92135 )
93136
94- _, err = tx.Exec (query, args... )
95- if err != nil {
96- return errors.Err (err)
137+ //There could be multiple conflicting rows between ObjectIDs. In the SELECT query we grab each row and their column
138+ // keys to be deleted here in a loop.
139+ for _, rowToDelete := range rowsToRemove {
140+ rowToDelete = append(rowToDelete, secondaryID)
141+ _, err = tx.Exec (query, rowToDelete... )
142+ if err != nil {
143+ return errors.Err (err)
144+ }
97145 }
98146 return nil
99147}
0 commit comments