Skip to content

Commit fadcbfa

Browse files
authored
Merge pull request #3 from lbryio/merge_changes
added support for 1 to 1 relations and added support for n unique key…
2 parents d180a09 + 29172e9 commit fadcbfa

1 file changed

Lines changed: 77 additions & 29 deletions

File tree

templates/singleton/boil_queries.tpl

Lines changed: 77 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)