dialect/sql/sqlgraph: changing the M2M creation semantic from Add to Connect

Thus, calling AddE with the same edge values, ignore and not throw an error.
For edge-schemas with extra fields, we maintain the same logic because we cannot
determine if the extra fields have different values that what exist in the database
This commit is contained in:
Ariel Mashraki
2022-11-20 12:26:40 +02:00
committed by Ariel Mashraki
parent 4c9d61cf16
commit aa3d21f01a
39 changed files with 5234 additions and 55 deletions

View File

@@ -777,7 +777,7 @@ func ConflictWhere(p *Predicate) ConflictOption {
}
}
// UpdateWhere allows setting the an update condition. Only rows
// UpdateWhere allows setting the update condition. Only rows
// for which this expression returns true will be updated.
func UpdateWhere(p *Predicate) ConflictOption {
return func(c *conflict) {
@@ -985,8 +985,10 @@ func (i *InsertBuilder) writeConflict(b *Builder) {
switch i.Dialect() {
case dialect.MySQL:
b.WriteString(" ON DUPLICATE KEY UPDATE ")
// Fallback to ResolveWithIgnore() as MySQL
// does not support the "DO NOTHING" clause.
if i.conflict.action.nothing {
b.AddError(fmt.Errorf("invalid CONFLICT action ('DO NOTHING')"))
i.OnConflict(ResolveWithIgnore())
}
case dialect.SQLite, dialect.Postgres:
b.WriteString(" ON CONFLICT")

View File

@@ -1287,7 +1287,8 @@ func (g *graph) addM2MEdges(ctx context.Context, ids []driver.Value, edges EdgeS
columns = edges[0].Columns
values = make([]any, 0, len(edges[0].Target.Fields))
)
// Specs are generated equally for all edges from the same type.
// Additional fields, such as edge-schema fields. Note, we use the first index,
// because Ent generates the same spec fields for all edges from the same type.
for _, f := range edges[0].Target.Fields {
values = append(values, f.Value)
columns = append(columns, f.Column)
@@ -1310,6 +1311,11 @@ func (g *graph) addM2MEdges(ctx context.Context, ids []driver.Value, edges EdgeS
}
}
}
// Ignore conflicts only if edges do not contain extra fields, because these fields
// can hold different values on different insertions (e.g. time.Now() or uuid.New()).
if len(edges[0].Target.Fields) == 0 {
insert.OnConflict(sql.DoNothing())
}
query, args := insert.Query()
if err := g.tx.Exec(ctx, query, args, nil); err != nil {
return fmt.Errorf("add m2m edge for table %s: %w", table, err)

View File

@@ -1129,7 +1129,7 @@ func TestCreateNode(t *testing.T) {
m.ExpectExec(escape("INSERT INTO `groups` (`name`) VALUES (?)")).
WithArgs("GitHub").
WillReturnResult(sqlmock.NewResult(1, 1))
m.ExpectExec(escape("INSERT INTO `group_users` (`group_id`, `user_id`) VALUES (?, ?)")).
m.ExpectExec(escape("INSERT INTO `group_users` (`group_id`, `user_id`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `group_id` = `group_users`.`group_id`, `user_id` = `group_users`.`user_id`")).
WithArgs(1, 2).
WillReturnResult(sqlmock.NewResult(1, 1))
m.ExpectCommit()
@@ -1175,7 +1175,7 @@ func TestCreateNode(t *testing.T) {
m.ExpectExec(escape("INSERT INTO `users` (`name`) VALUES (?)")).
WithArgs("mashraki").
WillReturnResult(sqlmock.NewResult(1, 1))
m.ExpectExec(escape("INSERT INTO `group_users` (`group_id`, `user_id`) VALUES (?, ?)")).
m.ExpectExec(escape("INSERT INTO `group_users` (`group_id`, `user_id`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `group_id` = `group_users`.`group_id`, `user_id` = `group_users`.`user_id`")).
WithArgs(2, 1).
WillReturnResult(sqlmock.NewResult(1, 1))
m.ExpectCommit()
@@ -1198,7 +1198,7 @@ func TestCreateNode(t *testing.T) {
m.ExpectExec(escape("INSERT INTO `users` (`name`) VALUES (?)")).
WithArgs("mashraki").
WillReturnResult(sqlmock.NewResult(1, 1))
m.ExpectExec(escape("INSERT INTO `user_friends` (`user_id`, `friend_id`) VALUES (?, ?), (?, ?)")).
m.ExpectExec(escape("INSERT INTO `user_friends` (`user_id`, `friend_id`) VALUES (?, ?), (?, ?) ON DUPLICATE KEY UPDATE `user_id` = `user_friends`.`user_id`, `friend_id` = `user_friends`.`friend_id`")).
WithArgs(1, 2, 2, 1).
WillReturnResult(sqlmock.NewResult(1, 1))
m.ExpectCommit()
@@ -1247,10 +1247,10 @@ func TestCreateNode(t *testing.T) {
m.ExpectExec(escape("INSERT INTO `users` (`name`) VALUES (?)")).
WithArgs("mashraki").
WillReturnResult(sqlmock.NewResult(1, 1))
m.ExpectExec(escape("INSERT INTO `group_users` (`group_id`, `user_id`) VALUES (?, ?), (?, ?)")).
m.ExpectExec(escape("INSERT INTO `group_users` (`group_id`, `user_id`) VALUES (?, ?), (?, ?) ON DUPLICATE KEY UPDATE `group_id` = `group_users`.`group_id`, `user_id` = `group_users`.`user_id`")).
WithArgs(4, 1, 5, 1).
WillReturnResult(sqlmock.NewResult(1, 1))
m.ExpectExec(escape("INSERT INTO `user_friends` (`user_id`, `friend_id`) VALUES (?, ?), (?, ?), (?, ?), (?, ?)")).
m.ExpectExec(escape("INSERT INTO `user_friends` (`user_id`, `friend_id`) VALUES (?, ?), (?, ?), (?, ?), (?, ?) ON DUPLICATE KEY UPDATE `user_id` = `user_friends`.`user_id`, `friend_id` = `user_friends`.`friend_id`")).
WithArgs(1, 2, 2, 1, 1, 3, 3, 1).
WillReturnResult(sqlmock.NewResult(1, 1))
m.ExpectCommit()
@@ -1260,7 +1260,7 @@ func TestCreateNode(t *testing.T) {
name: "schema",
spec: &CreateSpec{
Table: "users",
Schema: "mydb",
Schema: "test",
ID: &FieldSpec{Column: "id", Type: field.TypeInt},
Fields: []*FieldSpec{
{Column: "age", Type: field.TypeInt, Value: 30},
@@ -1268,7 +1268,7 @@ func TestCreateNode(t *testing.T) {
},
},
expect: func(m sqlmock.Sqlmock) {
m.ExpectExec(escape("INSERT INTO `mydb`.`users` (`age`, `name`) VALUES (?, ?)")).
m.ExpectExec(escape("INSERT INTO `test`.`users` (`age`, `name`) VALUES (?, ?)")).
WithArgs(30, "a8m").
WillReturnResult(sqlmock.NewResult(1, 1))
},