added support for 1 to 1 relations and added support for n unique key… #3

Merged
tiger5226 merged 2 commits from merge_changes into master 2019-02-08 01:51:36 +01:00
Showing only changes of commit eea3d349a7 - Show all commits

View file

@ -27,11 +27,15 @@ func mergeModels(tx boil.Executor, primaryID uint64, secondaryID uint64, foreign
var err error var err error
for _, conflict := range conflictingKeys { for _, conflict := range conflictingKeys {
err = deleteConflictsBeforeMerge(tx, conflict, primaryID, secondaryID) if len(conflict.columns) == 1 && conflict.columns[0] == conflict.objectIdColumn {
if err != nil { err = deleteOneToOneConflictsBeforeMerge(tx, conflict, primaryID, secondaryID)
return err } else {
} err = deleteOneToManyConflictsBeforeMerge(tx, conflict, primaryID, secondaryID)
} }
if err != nil {
return err
}
}
for _, fk := range foreignKeys { for _, fk := range foreignKeys {
// TODO: use NewQuery here, not plain sql // TODO: use NewQuery here, not plain sql
@ -48,53 +52,97 @@ func mergeModels(tx boil.Executor, primaryID uint64, secondaryID uint64, foreign
return checkMerge(tx, foreignKeys) return checkMerge(tx, foreignKeys)
} }
func deleteConflictsBeforeMerge(tx boil.Executor, conflict conflictingUniqueKey, primaryID uint64, secondaryID uint64) error { func deleteOneToOneConflictsBeforeMerge(tx boil.Executor, conflict conflictingUniqueKey, primaryID uint64, secondaryID uint64) error {
conflictingColumns := strmangle.SetComplement(conflict.columns, []string{conflict.objectIdColumn})
if len(conflictingColumns) < 1 {
return nil
} else if len(conflictingColumns) > 1 {
return errors.Err("this doesnt work for unique keys with more than two columns (yet)")
}
query := fmt.Sprintf( query := fmt.Sprintf(
"SELECT %s FROM %s WHERE %s IN (%s) GROUP BY %s HAVING count(distinct %s) > 1", "SELECT COUNT(*) FROM %s WHERE %s IN (%s)",
conflictingColumns[0], conflict.table, conflict.objectIdColumn, conflict.table, conflict.objectIdColumn,
strmangle.Placeholders(dialect.IndexPlaceholders, 2, 1, 1), strmangle.Placeholders(dialect.IndexPlaceholders, 2, 1, 1),
conflictingColumns[0], conflict.objectIdColumn,
) )
rows, err := tx.Query(query, primaryID, secondaryID) var count int
defer rows.Close() err := tx.QueryRow(query, primaryID, secondaryID).Scan(&count)
if err != nil { if err != nil {
return errors.Err(err) return errors.Err(err)
} }
args := []interface{}{secondaryID} if count > 2 {
for rows.Next() { return errors.Err("it should not be possible to have more than two rows here")
var value string } else if count != 2 {
err = rows.Scan(&value) return nil // no conflicting rows
if err != nil {
return errors.Err(err)
}
args = append(args, value)
}
// if no rows found, no need to delete anything
if len(args) < 2 {
return nil
} }
query = fmt.Sprintf( query = fmt.Sprintf(
"DELETE FROM %s WHERE %s = %s AND %s IN (%s)", "DELETE FROM %s WHERE %s = %s",
conflict.table, conflict.objectIdColumn, strmangle.Placeholders(dialect.IndexPlaceholders, 1, 1, 1), conflict.table, conflict.objectIdColumn, strmangle.Placeholders(dialect.IndexPlaceholders, 1, 1, 1),
conflictingColumns[0], strmangle.Placeholders(dialect.IndexPlaceholders, len(args)-1, 2, 1),
) )
_, err = tx.Exec(query, args...) _, err = tx.Exec(query, secondaryID)
return errors.Err(err)
}
func deleteOneToManyConflictsBeforeMerge(tx boil.Executor, conflict conflictingUniqueKey, primaryID uint64, secondaryID uint64) error {
conflictingColumns := strmangle.SetComplement(conflict.columns, []string{conflict.objectIdColumn})
query := fmt.Sprintf(
"SELECT %s FROM %s WHERE %s IN (%s) GROUP BY %s HAVING count(distinct %s) > 1",
strings.Join(conflictingColumns, ","), conflict.table, conflict.objectIdColumn,
strmangle.Placeholders(dialect.IndexPlaceholders, 2, 1, 1),
strings.Join(conflictingColumns, ","), conflict.objectIdColumn,
)
//The selectParams should be the ObjectIDs to search for regarding the conflict.
rows, err := tx.Query(query, primaryID, secondaryID)
if err != nil { if err != nil {
return errors.Err(err) return errors.Err(err)
} }
//Since we don't don't know if advance how many columns the query returns, we have dynamically assign them to be
// used in the delete query.
colNames, err := rows.Columns()
if err != nil {
log.Fatal(err)
}
//Each row result of the query needs to be removed for being a conflicting row. Store each row's keys in an array.
var rowsToRemove = [][]interface{}(nil)
for rows.Next() {
//Set pointers for dynamic scan
iColPtrs := make([]interface{}, len(colNames))
for i := 0; i < len(colNames); i++ {
s := string("")
iColPtrs[i] = &s
}
//Dynamically scan n columns
err = rows.Scan(iColPtrs...)
if err != nil {
return errors.Err(err)
}
//Grab scanned values for query arguments
iCol := make([]interface{}, len(colNames))
for i, col := range iColPtrs {
x := col.(*string)
iCol[i] = *x
}
rowsToRemove = append(rowsToRemove, iCol)
}
defer rows.Close()
//This query will adjust dynamically depending on the number of conflicting keys, adding AND expressions for each
// key to ensure the right conflicting rows are deleted.
lyoshenka commented 2018-09-05 17:43:58 +02:00 (Migrated from github.com)
Review

why don't we know in advance how many columns will be returned. isn't it just len(conflictingColumns)?

why don't we know in advance how many columns will be returned. isn't it just `len(conflictingColumns)`?
tiger5226 commented 2018-09-06 01:49:18 +02:00 (Migrated from github.com)
Review

We do, but we can't predict the columns in the code. So we have to be dynamic. One query might have 1, another might have 5. I can't create a generic struct for scanning purposes. Front the research I did online, they said to use this pointer solution for dynamic scans.

We do, but we can't predict the columns in the code. So we have to be dynamic. One query might have 1, another might have 5. I can't create a generic struct for scanning purposes. Front the research I did online, they said to use this pointer solution for dynamic scans.
tiger5226 commented 2018-09-08 17:52:47 +02:00 (Migrated from github.com)
Review

@lyoshenka is this acceptable? or were you just curious? If you approve I would like deploy this fix.

@lyoshenka is this acceptable? or were you just curious? If you approve I would like deploy this fix.
query = fmt.Sprintf(
"DELETE FROM %s %s",
conflict.table,
"WHERE "+strings.Join(conflict.columns, " = ? AND ")+" = ?",
)
//There could be multiple conflicting rows between ObjectIDs. In the SELECT query we grab each row and their column
// keys to be deleted here in a loop.
for _, rowToDelete := range rowsToRemove {
rowToDelete = append(rowToDelete, secondaryID)
_, err = tx.Exec(query, rowToDelete...)
if err != nil {
return errors.Err(err)
}
}
return nil return nil
} }